Diffdown is a real-time collaborative Markdown editor/previewer built on the AT Protocol
diffdown.com
1'use strict';
2
3var state = require('@codemirror/state');
4var styleMod = require('style-mod');
5var w3cKeyname = require('w3c-keyname');
6var elt = require('crelt');
7
8let nav = typeof navigator != "undefined" ? navigator : { userAgent: "", vendor: "", platform: "" };
9let doc = typeof document != "undefined" ? document : { documentElement: { style: {} } };
10const ie_edge = /Edge\/(\d+)/.exec(nav.userAgent);
11const ie_upto10 = /MSIE \d/.test(nav.userAgent);
12const ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(nav.userAgent);
13const ie = !!(ie_upto10 || ie_11up || ie_edge);
14const gecko = !ie && /gecko\/(\d+)/i.test(nav.userAgent);
15const chrome = !ie && /Chrome\/(\d+)/.exec(nav.userAgent);
16const webkit = "webkitFontSmoothing" in doc.documentElement.style;
17const safari = !ie && /Apple Computer/.test(nav.vendor);
18const ios = safari && (/Mobile\/\w+/.test(nav.userAgent) || nav.maxTouchPoints > 2);
19var browser = {
20 mac: ios || /Mac/.test(nav.platform),
21 windows: /Win/.test(nav.platform),
22 linux: /Linux|X11/.test(nav.platform),
23 ie,
24 ie_version: ie_upto10 ? doc.documentMode || 6 : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0,
25 gecko,
26 gecko_version: gecko ? +(/Firefox\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
27 chrome: !!chrome,
28 chrome_version: chrome ? +chrome[1] : 0,
29 ios,
30 android: /Android\b/.test(nav.userAgent),
31 webkit,
32 webkit_version: webkit ? +(/\bAppleWebKit\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
33 safari,
34 safari_version: safari ? +(/\bVersion\/(\d+(\.\d+)?)/.exec(nav.userAgent) || [0, 0])[1] : 0,
35 tabSize: doc.documentElement.style.tabSize != null ? "tab-size" : "-moz-tab-size"
36};
37
38function combineAttrs(source, target) {
39 for (let name in source) {
40 if (name == "class" && target.class)
41 target.class += " " + source.class;
42 else if (name == "style" && target.style)
43 target.style += ";" + source.style;
44 else
45 target[name] = source[name];
46 }
47 return target;
48}
49const noAttrs = Object.create(null);
50function attrsEq(a, b, ignore) {
51 if (a == b)
52 return true;
53 if (!a)
54 a = noAttrs;
55 if (!b)
56 b = noAttrs;
57 let keysA = Object.keys(a), keysB = Object.keys(b);
58 if (keysA.length - (ignore && keysA.indexOf(ignore) > -1 ? 1 : 0) !=
59 keysB.length - (ignore && keysB.indexOf(ignore) > -1 ? 1 : 0))
60 return false;
61 for (let key of keysA) {
62 if (key != ignore && (keysB.indexOf(key) == -1 || a[key] !== b[key]))
63 return false;
64 }
65 return true;
66}
67function setAttrs(dom, attrs) {
68 for (let i = dom.attributes.length - 1; i >= 0; i--) {
69 let name = dom.attributes[i].name;
70 if (attrs[name] == null)
71 dom.removeAttribute(name);
72 }
73 for (let name in attrs) {
74 let value = attrs[name];
75 if (name == "style")
76 dom.style.cssText = value;
77 else if (dom.getAttribute(name) != value)
78 dom.setAttribute(name, value);
79 }
80}
81function updateAttrs(dom, prev, attrs) {
82 let changed = false;
83 if (prev)
84 for (let name in prev)
85 if (!(attrs && name in attrs)) {
86 changed = true;
87 if (name == "style")
88 dom.style.cssText = "";
89 else
90 dom.removeAttribute(name);
91 }
92 if (attrs)
93 for (let name in attrs)
94 if (!(prev && prev[name] == attrs[name])) {
95 changed = true;
96 if (name == "style")
97 dom.style.cssText = attrs[name];
98 else
99 dom.setAttribute(name, attrs[name]);
100 }
101 return changed;
102}
103function getAttrs(dom) {
104 let attrs = Object.create(null);
105 for (let i = 0; i < dom.attributes.length; i++) {
106 let attr = dom.attributes[i];
107 attrs[attr.name] = attr.value;
108 }
109 return attrs;
110}
111
112/**
113Widgets added to the content are described by subclasses of this
114class. Using a description object like that makes it possible to
115delay creating of the DOM structure for a widget until it is
116needed, and to avoid redrawing widgets even if the decorations
117that define them are recreated.
118*/
119class WidgetType {
120 /**
121 Compare this instance to another instance of the same type.
122 (TypeScript can't express this, but only instances of the same
123 specific class will be passed to this method.) This is used to
124 avoid redrawing widgets when they are replaced by a new
125 decoration of the same type. The default implementation just
126 returns `false`, which will cause new instances of the widget to
127 always be redrawn.
128 */
129 eq(widget) { return false; }
130 /**
131 Update a DOM element created by a widget of the same type (but
132 different, non-`eq` content) to reflect this widget. May return
133 true to indicate that it could update, false to indicate it
134 couldn't (in which case the widget will be redrawn). The default
135 implementation just returns false.
136 */
137 updateDOM(dom, view) { return false; }
138 /**
139 @internal
140 */
141 compare(other) {
142 return this == other || this.constructor == other.constructor && this.eq(other);
143 }
144 /**
145 The estimated height this widget will have, to be used when
146 estimating the height of content that hasn't been drawn. May
147 return -1 to indicate you don't know. The default implementation
148 returns -1.
149 */
150 get estimatedHeight() { return -1; }
151 /**
152 For inline widgets that are displayed inline (as opposed to
153 `inline-block`) and introduce line breaks (through `<br>` tags
154 or textual newlines), this must indicate the amount of line
155 breaks they introduce. Defaults to 0.
156 */
157 get lineBreaks() { return 0; }
158 /**
159 Can be used to configure which kinds of events inside the widget
160 should be ignored by the editor. The default is to ignore all
161 events.
162 */
163 ignoreEvent(event) { return true; }
164 /**
165 Override the way screen coordinates for positions at/in the
166 widget are found. `pos` will be the offset into the widget, and
167 `side` the side of the position that is being queried—less than
168 zero for before, greater than zero for after, and zero for
169 directly at that position.
170 */
171 coordsAt(dom, pos, side) { return null; }
172 /**
173 @internal
174 */
175 get isHidden() { return false; }
176 /**
177 @internal
178 */
179 get editable() { return false; }
180 /**
181 This is called when the an instance of the widget is removed
182 from the editor view.
183 */
184 destroy(dom) { }
185}
186/**
187The different types of blocks that can occur in an editor view.
188*/
189exports.BlockType = void 0;
190(function (BlockType) {
191 /**
192 A line of text.
193 */
194 BlockType[BlockType["Text"] = 0] = "Text";
195 /**
196 A block widget associated with the position after it.
197 */
198 BlockType[BlockType["WidgetBefore"] = 1] = "WidgetBefore";
199 /**
200 A block widget associated with the position before it.
201 */
202 BlockType[BlockType["WidgetAfter"] = 2] = "WidgetAfter";
203 /**
204 A block widget [replacing](https://codemirror.net/6/docs/ref/#view.Decoration^replace) a range of content.
205 */
206 BlockType[BlockType["WidgetRange"] = 3] = "WidgetRange";
207})(exports.BlockType || (exports.BlockType = {}));
208/**
209A decoration provides information on how to draw or style a piece
210of content. You'll usually use it wrapped in a
211[`Range`](https://codemirror.net/6/docs/ref/#state.Range), which adds a start and end position.
212@nonabstract
213*/
214class Decoration extends state.RangeValue {
215 constructor(
216 /**
217 @internal
218 */
219 startSide,
220 /**
221 @internal
222 */
223 endSide,
224 /**
225 @internal
226 */
227 widget,
228 /**
229 The config object used to create this decoration. You can
230 include additional properties in there to store metadata about
231 your decoration.
232 */
233 spec) {
234 super();
235 this.startSide = startSide;
236 this.endSide = endSide;
237 this.widget = widget;
238 this.spec = spec;
239 }
240 /**
241 @internal
242 */
243 get heightRelevant() { return false; }
244 /**
245 Create a mark decoration, which influences the styling of the
246 content in its range. Nested mark decorations will cause nested
247 DOM elements to be created. Nesting order is determined by
248 precedence of the [facet](https://codemirror.net/6/docs/ref/#view.EditorView^decorations), with
249 the higher-precedence decorations creating the inner DOM nodes.
250 Such elements are split on line boundaries and on the boundaries
251 of lower-precedence decorations.
252 */
253 static mark(spec) {
254 return new MarkDecoration(spec);
255 }
256 /**
257 Create a widget decoration, which displays a DOM element at the
258 given position.
259 */
260 static widget(spec) {
261 let side = Math.max(-10000, Math.min(10000, spec.side || 0)), block = !!spec.block;
262 side += (block && !spec.inlineOrder)
263 ? (side > 0 ? 300000000 /* Side.BlockAfter */ : -400000000 /* Side.BlockBefore */)
264 : (side > 0 ? 100000000 /* Side.InlineAfter */ : -100000000 /* Side.InlineBefore */);
265 return new PointDecoration(spec, side, side, block, spec.widget || null, false);
266 }
267 /**
268 Create a replace decoration which replaces the given range with
269 a widget, or simply hides it.
270 */
271 static replace(spec) {
272 let block = !!spec.block, startSide, endSide;
273 if (spec.isBlockGap) {
274 startSide = -500000000 /* Side.GapStart */;
275 endSide = 400000000 /* Side.GapEnd */;
276 }
277 else {
278 let { start, end } = getInclusive(spec, block);
279 startSide = (start ? (block ? -300000000 /* Side.BlockIncStart */ : -1 /* Side.InlineIncStart */) : 500000000 /* Side.NonIncStart */) - 1;
280 endSide = (end ? (block ? 200000000 /* Side.BlockIncEnd */ : 1 /* Side.InlineIncEnd */) : -600000000 /* Side.NonIncEnd */) + 1;
281 }
282 return new PointDecoration(spec, startSide, endSide, block, spec.widget || null, true);
283 }
284 /**
285 Create a line decoration, which can add DOM attributes to the
286 line starting at the given position.
287 */
288 static line(spec) {
289 return new LineDecoration(spec);
290 }
291 /**
292 Build a [`DecorationSet`](https://codemirror.net/6/docs/ref/#view.DecorationSet) from the given
293 decorated range or ranges. If the ranges aren't already sorted,
294 pass `true` for `sort` to make the library sort them for you.
295 */
296 static set(of, sort = false) {
297 return state.RangeSet.of(of, sort);
298 }
299 /**
300 @internal
301 */
302 hasHeight() { return this.widget ? this.widget.estimatedHeight > -1 : false; }
303}
304/**
305The empty set of decorations.
306*/
307Decoration.none = state.RangeSet.empty;
308class MarkDecoration extends Decoration {
309 constructor(spec) {
310 let { start, end } = getInclusive(spec);
311 super(start ? -1 /* Side.InlineIncStart */ : 500000000 /* Side.NonIncStart */, end ? 1 /* Side.InlineIncEnd */ : -600000000 /* Side.NonIncEnd */, null, spec);
312 this.tagName = spec.tagName || "span";
313 this.attrs = spec.class && spec.attributes ? combineAttrs(spec.attributes, { class: spec.class })
314 : spec.class ? { class: spec.class } : spec.attributes || noAttrs;
315 }
316 eq(other) {
317 return this == other || other instanceof MarkDecoration && this.tagName == other.tagName && attrsEq(this.attrs, other.attrs);
318 }
319 range(from, to = from) {
320 if (from >= to)
321 throw new RangeError("Mark decorations may not be empty");
322 return super.range(from, to);
323 }
324}
325MarkDecoration.prototype.point = false;
326class LineDecoration extends Decoration {
327 constructor(spec) {
328 super(-200000000 /* Side.Line */, -200000000 /* Side.Line */, null, spec);
329 }
330 eq(other) {
331 return other instanceof LineDecoration &&
332 this.spec.class == other.spec.class &&
333 attrsEq(this.spec.attributes, other.spec.attributes);
334 }
335 range(from, to = from) {
336 if (to != from)
337 throw new RangeError("Line decoration ranges must be zero-length");
338 return super.range(from, to);
339 }
340}
341LineDecoration.prototype.mapMode = state.MapMode.TrackBefore;
342LineDecoration.prototype.point = true;
343class PointDecoration extends Decoration {
344 constructor(spec, startSide, endSide, block, widget, isReplace) {
345 super(startSide, endSide, widget, spec);
346 this.block = block;
347 this.isReplace = isReplace;
348 this.mapMode = !block ? state.MapMode.TrackDel : startSide <= 0 ? state.MapMode.TrackBefore : state.MapMode.TrackAfter;
349 }
350 // Only relevant when this.block == true
351 get type() {
352 return this.startSide != this.endSide ? exports.BlockType.WidgetRange
353 : this.startSide <= 0 ? exports.BlockType.WidgetBefore : exports.BlockType.WidgetAfter;
354 }
355 get heightRelevant() {
356 return this.block || !!this.widget && (this.widget.estimatedHeight >= 5 || this.widget.lineBreaks > 0);
357 }
358 eq(other) {
359 return other instanceof PointDecoration &&
360 widgetsEq(this.widget, other.widget) &&
361 this.block == other.block &&
362 this.startSide == other.startSide && this.endSide == other.endSide;
363 }
364 range(from, to = from) {
365 if (this.isReplace && (from > to || (from == to && this.startSide > 0 && this.endSide <= 0)))
366 throw new RangeError("Invalid range for replacement decoration");
367 if (!this.isReplace && to != from)
368 throw new RangeError("Widget decorations can only have zero-length ranges");
369 return super.range(from, to);
370 }
371}
372PointDecoration.prototype.point = true;
373function getInclusive(spec, block = false) {
374 let { inclusiveStart: start, inclusiveEnd: end } = spec;
375 if (start == null)
376 start = spec.inclusive;
377 if (end == null)
378 end = spec.inclusive;
379 return { start: start !== null && start !== void 0 ? start : block, end: end !== null && end !== void 0 ? end : block };
380}
381function widgetsEq(a, b) {
382 return a == b || !!(a && b && a.compare(b));
383}
384function addRange(from, to, ranges, margin = 0) {
385 let last = ranges.length - 1;
386 if (last >= 0 && ranges[last] + margin >= from)
387 ranges[last] = Math.max(ranges[last], to);
388 else
389 ranges.push(from, to);
390}
391/**
392A block wrapper defines a DOM node that wraps lines or other block
393wrappers at the top of the document. It affects any line or block
394widget that starts inside its range, including blocks starting
395directly at `from` but not including `to`.
396*/
397class BlockWrapper extends state.RangeValue {
398 constructor(tagName, attributes) {
399 super();
400 this.tagName = tagName;
401 this.attributes = attributes;
402 }
403 eq(other) {
404 return other == this ||
405 other instanceof BlockWrapper && this.tagName == other.tagName && attrsEq(this.attributes, other.attributes);
406 }
407 /**
408 Create a block wrapper object with the given tag name and
409 attributes.
410 */
411 static create(spec) {
412 return new BlockWrapper(spec.tagName, spec.attributes || noAttrs);
413 }
414 /**
415 Create a range set from the given block wrapper ranges.
416 */
417 static set(of, sort = false) {
418 return state.RangeSet.of(of, sort);
419 }
420}
421BlockWrapper.prototype.startSide = BlockWrapper.prototype.endSide = -1;
422
423function getSelection(root) {
424 let target;
425 // Browsers differ on whether shadow roots have a getSelection
426 // method. If it exists, use that, otherwise, call it on the
427 // document.
428 if (root.nodeType == 11) { // Shadow root
429 target = root.getSelection ? root : root.ownerDocument;
430 }
431 else {
432 target = root;
433 }
434 return target.getSelection();
435}
436function contains(dom, node) {
437 return node ? dom == node || dom.contains(node.nodeType != 1 ? node.parentNode : node) : false;
438}
439function hasSelection(dom, selection) {
440 if (!selection.anchorNode)
441 return false;
442 try {
443 // Firefox will raise 'permission denied' errors when accessing
444 // properties of `sel.anchorNode` when it's in a generated CSS
445 // element.
446 return contains(dom, selection.anchorNode);
447 }
448 catch (_) {
449 return false;
450 }
451}
452function clientRectsFor(dom) {
453 if (dom.nodeType == 3)
454 return textRange(dom, 0, dom.nodeValue.length).getClientRects();
455 else if (dom.nodeType == 1)
456 return dom.getClientRects();
457 else
458 return [];
459}
460// Scans forward and backward through DOM positions equivalent to the
461// given one to see if the two are in the same place (i.e. after a
462// text node vs at the end of that text node)
463function isEquivalentPosition(node, off, targetNode, targetOff) {
464 return targetNode ? (scanFor(node, off, targetNode, targetOff, -1) ||
465 scanFor(node, off, targetNode, targetOff, 1)) : false;
466}
467function domIndex(node) {
468 for (var index = 0;; index++) {
469 node = node.previousSibling;
470 if (!node)
471 return index;
472 }
473}
474function isBlockElement(node) {
475 return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
476}
477function scanFor(node, off, targetNode, targetOff, dir) {
478 for (;;) {
479 if (node == targetNode && off == targetOff)
480 return true;
481 if (off == (dir < 0 ? 0 : maxOffset(node))) {
482 if (node.nodeName == "DIV")
483 return false;
484 let parent = node.parentNode;
485 if (!parent || parent.nodeType != 1)
486 return false;
487 off = domIndex(node) + (dir < 0 ? 0 : 1);
488 node = parent;
489 }
490 else if (node.nodeType == 1) {
491 node = node.childNodes[off + (dir < 0 ? -1 : 0)];
492 if (node.nodeType == 1 && node.contentEditable == "false")
493 return false;
494 off = dir < 0 ? maxOffset(node) : 0;
495 }
496 else {
497 return false;
498 }
499 }
500}
501function maxOffset(node) {
502 return node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length;
503}
504function flattenRect(rect, left) {
505 let x = left ? rect.left : rect.right;
506 return { left: x, right: x, top: rect.top, bottom: rect.bottom };
507}
508function windowRect(win) {
509 let vp = win.visualViewport;
510 if (vp)
511 return {
512 left: 0, right: vp.width,
513 top: 0, bottom: vp.height
514 };
515 return { left: 0, right: win.innerWidth,
516 top: 0, bottom: win.innerHeight };
517}
518function getScale(elt, rect) {
519 let scaleX = rect.width / elt.offsetWidth;
520 let scaleY = rect.height / elt.offsetHeight;
521 if (scaleX > 0.995 && scaleX < 1.005 || !isFinite(scaleX) || Math.abs(rect.width - elt.offsetWidth) < 1)
522 scaleX = 1;
523 if (scaleY > 0.995 && scaleY < 1.005 || !isFinite(scaleY) || Math.abs(rect.height - elt.offsetHeight) < 1)
524 scaleY = 1;
525 return { scaleX, scaleY };
526}
527function scrollRectIntoView(dom, rect, side, x, y, xMargin, yMargin, ltr) {
528 let doc = dom.ownerDocument, win = doc.defaultView || window;
529 for (let cur = dom, stop = false; cur && !stop;) {
530 if (cur.nodeType == 1) { // Element
531 let bounding, top = cur == doc.body;
532 let scaleX = 1, scaleY = 1;
533 if (top) {
534 bounding = windowRect(win);
535 }
536 else {
537 if (/^(fixed|sticky)$/.test(getComputedStyle(cur).position))
538 stop = true;
539 if (cur.scrollHeight <= cur.clientHeight && cur.scrollWidth <= cur.clientWidth) {
540 cur = cur.assignedSlot || cur.parentNode;
541 continue;
542 }
543 let rect = cur.getBoundingClientRect();
544 ({ scaleX, scaleY } = getScale(cur, rect));
545 // Make sure scrollbar width isn't included in the rectangle
546 bounding = { left: rect.left, right: rect.left + cur.clientWidth * scaleX,
547 top: rect.top, bottom: rect.top + cur.clientHeight * scaleY };
548 }
549 let moveX = 0, moveY = 0;
550 if (y == "nearest") {
551 if (rect.top < bounding.top) {
552 moveY = rect.top - (bounding.top + yMargin);
553 if (side > 0 && rect.bottom > bounding.bottom + moveY)
554 moveY = rect.bottom - bounding.bottom + yMargin;
555 }
556 else if (rect.bottom > bounding.bottom) {
557 moveY = rect.bottom - bounding.bottom + yMargin;
558 if (side < 0 && (rect.top - moveY) < bounding.top)
559 moveY = rect.top - (bounding.top + yMargin);
560 }
561 }
562 else {
563 let rectHeight = rect.bottom - rect.top, boundingHeight = bounding.bottom - bounding.top;
564 let targetTop = y == "center" && rectHeight <= boundingHeight ? rect.top + rectHeight / 2 - boundingHeight / 2 :
565 y == "start" || y == "center" && side < 0 ? rect.top - yMargin :
566 rect.bottom - boundingHeight + yMargin;
567 moveY = targetTop - bounding.top;
568 }
569 if (x == "nearest") {
570 if (rect.left < bounding.left) {
571 moveX = rect.left - (bounding.left + xMargin);
572 if (side > 0 && rect.right > bounding.right + moveX)
573 moveX = rect.right - bounding.right + xMargin;
574 }
575 else if (rect.right > bounding.right) {
576 moveX = rect.right - bounding.right + xMargin;
577 if (side < 0 && rect.left < bounding.left + moveX)
578 moveX = rect.left - (bounding.left + xMargin);
579 }
580 }
581 else {
582 let targetLeft = x == "center" ? rect.left + (rect.right - rect.left) / 2 - (bounding.right - bounding.left) / 2 :
583 (x == "start") == ltr ? rect.left - xMargin :
584 rect.right - (bounding.right - bounding.left) + xMargin;
585 moveX = targetLeft - bounding.left;
586 }
587 if (moveX || moveY) {
588 if (top) {
589 win.scrollBy(moveX, moveY);
590 }
591 else {
592 let movedX = 0, movedY = 0;
593 if (moveY) {
594 let start = cur.scrollTop;
595 cur.scrollTop += moveY / scaleY;
596 movedY = (cur.scrollTop - start) * scaleY;
597 }
598 if (moveX) {
599 let start = cur.scrollLeft;
600 cur.scrollLeft += moveX / scaleX;
601 movedX = (cur.scrollLeft - start) * scaleX;
602 }
603 rect = { left: rect.left - movedX, top: rect.top - movedY,
604 right: rect.right - movedX, bottom: rect.bottom - movedY };
605 if (movedX && Math.abs(movedX - moveX) < 1)
606 x = "nearest";
607 if (movedY && Math.abs(movedY - moveY) < 1)
608 y = "nearest";
609 }
610 }
611 if (top)
612 break;
613 if (rect.top < bounding.top || rect.bottom > bounding.bottom ||
614 rect.left < bounding.left || rect.right > bounding.right)
615 rect = { left: Math.max(rect.left, bounding.left), right: Math.min(rect.right, bounding.right),
616 top: Math.max(rect.top, bounding.top), bottom: Math.min(rect.bottom, bounding.bottom) };
617 cur = cur.assignedSlot || cur.parentNode;
618 }
619 else if (cur.nodeType == 11) { // A shadow root
620 cur = cur.host;
621 }
622 else {
623 break;
624 }
625 }
626}
627function scrollableParents(dom, getX = true) {
628 let doc = dom.ownerDocument, x = null, y = null;
629 for (let cur = dom.parentNode; cur;) {
630 if (cur == doc.body || ((!getX || x) && y)) {
631 break;
632 }
633 else if (cur.nodeType == 1) {
634 if (!y && cur.scrollHeight > cur.clientHeight)
635 y = cur;
636 if (getX && !x && cur.scrollWidth > cur.clientWidth)
637 x = cur;
638 cur = cur.assignedSlot || cur.parentNode;
639 }
640 else if (cur.nodeType == 11) {
641 cur = cur.host;
642 }
643 else {
644 break;
645 }
646 }
647 return { x, y };
648}
649class DOMSelectionState {
650 constructor() {
651 this.anchorNode = null;
652 this.anchorOffset = 0;
653 this.focusNode = null;
654 this.focusOffset = 0;
655 }
656 eq(domSel) {
657 return this.anchorNode == domSel.anchorNode && this.anchorOffset == domSel.anchorOffset &&
658 this.focusNode == domSel.focusNode && this.focusOffset == domSel.focusOffset;
659 }
660 setRange(range) {
661 let { anchorNode, focusNode } = range;
662 // Clip offsets to node size to avoid crashes when Safari reports bogus offsets (#1152)
663 this.set(anchorNode, Math.min(range.anchorOffset, anchorNode ? maxOffset(anchorNode) : 0), focusNode, Math.min(range.focusOffset, focusNode ? maxOffset(focusNode) : 0));
664 }
665 set(anchorNode, anchorOffset, focusNode, focusOffset) {
666 this.anchorNode = anchorNode;
667 this.anchorOffset = anchorOffset;
668 this.focusNode = focusNode;
669 this.focusOffset = focusOffset;
670 }
671}
672let preventScrollSupported = null;
673// Safari 26 breaks preventScroll support
674if (browser.safari && browser.safari_version >= 26)
675 preventScrollSupported = false;
676// Feature-detects support for .focus({preventScroll: true}), and uses
677// a fallback kludge when not supported.
678function focusPreventScroll(dom) {
679 if (dom.setActive)
680 return dom.setActive(); // in IE
681 if (preventScrollSupported)
682 return dom.focus(preventScrollSupported);
683 let stack = [];
684 for (let cur = dom; cur; cur = cur.parentNode) {
685 stack.push(cur, cur.scrollTop, cur.scrollLeft);
686 if (cur == cur.ownerDocument)
687 break;
688 }
689 dom.focus(preventScrollSupported == null ? {
690 get preventScroll() {
691 preventScrollSupported = { preventScroll: true };
692 return true;
693 }
694 } : undefined);
695 if (!preventScrollSupported) {
696 preventScrollSupported = false;
697 for (let i = 0; i < stack.length;) {
698 let elt = stack[i++], top = stack[i++], left = stack[i++];
699 if (elt.scrollTop != top)
700 elt.scrollTop = top;
701 if (elt.scrollLeft != left)
702 elt.scrollLeft = left;
703 }
704 }
705}
706let scratchRange;
707function textRange(node, from, to = from) {
708 let range = scratchRange || (scratchRange = document.createRange());
709 range.setEnd(node, to);
710 range.setStart(node, from);
711 return range;
712}
713function dispatchKey(elt, name, code, mods) {
714 let options = { key: name, code: name, keyCode: code, which: code, cancelable: true };
715 if (mods)
716 ({ altKey: options.altKey, ctrlKey: options.ctrlKey, shiftKey: options.shiftKey, metaKey: options.metaKey } = mods);
717 let down = new KeyboardEvent("keydown", options);
718 down.synthetic = true;
719 elt.dispatchEvent(down);
720 let up = new KeyboardEvent("keyup", options);
721 up.synthetic = true;
722 elt.dispatchEvent(up);
723 return down.defaultPrevented || up.defaultPrevented;
724}
725function getRoot(node) {
726 while (node) {
727 if (node && (node.nodeType == 9 || node.nodeType == 11 && node.host))
728 return node;
729 node = node.assignedSlot || node.parentNode;
730 }
731 return null;
732}
733function atElementStart(doc, selection) {
734 let node = selection.focusNode, offset = selection.focusOffset;
735 if (!node || selection.anchorNode != node || selection.anchorOffset != offset)
736 return false;
737 // Safari can report bogus offsets (#1152)
738 offset = Math.min(offset, maxOffset(node));
739 for (;;) {
740 if (offset) {
741 if (node.nodeType != 1)
742 return false;
743 let prev = node.childNodes[offset - 1];
744 if (prev.contentEditable == "false")
745 offset--;
746 else {
747 node = prev;
748 offset = maxOffset(node);
749 }
750 }
751 else if (node == doc) {
752 return true;
753 }
754 else {
755 offset = domIndex(node);
756 node = node.parentNode;
757 }
758 }
759}
760function isScrolledToBottom(elt) {
761 if (elt instanceof Window)
762 return elt.pageYOffset > Math.max(0, elt.document.documentElement.scrollHeight - elt.innerHeight - 4);
763 return elt.scrollTop > Math.max(1, elt.scrollHeight - elt.clientHeight - 4);
764}
765function textNodeBefore(startNode, startOffset) {
766 for (let node = startNode, offset = startOffset;;) {
767 if (node.nodeType == 3 && offset > 0) {
768 return { node: node, offset: offset };
769 }
770 else if (node.nodeType == 1 && offset > 0) {
771 if (node.contentEditable == "false")
772 return null;
773 node = node.childNodes[offset - 1];
774 offset = maxOffset(node);
775 }
776 else if (node.parentNode && !isBlockElement(node)) {
777 offset = domIndex(node);
778 node = node.parentNode;
779 }
780 else {
781 return null;
782 }
783 }
784}
785function textNodeAfter(startNode, startOffset) {
786 for (let node = startNode, offset = startOffset;;) {
787 if (node.nodeType == 3 && offset < node.nodeValue.length) {
788 return { node: node, offset: offset };
789 }
790 else if (node.nodeType == 1 && offset < node.childNodes.length) {
791 if (node.contentEditable == "false")
792 return null;
793 node = node.childNodes[offset];
794 offset = 0;
795 }
796 else if (node.parentNode && !isBlockElement(node)) {
797 offset = domIndex(node) + 1;
798 node = node.parentNode;
799 }
800 else {
801 return null;
802 }
803 }
804}
805class DOMPos {
806 constructor(node, offset, precise = true) {
807 this.node = node;
808 this.offset = offset;
809 this.precise = precise;
810 }
811 static before(dom, precise) { return new DOMPos(dom.parentNode, domIndex(dom), precise); }
812 static after(dom, precise) { return new DOMPos(dom.parentNode, domIndex(dom) + 1, precise); }
813}
814
815/**
816Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
817*/
818exports.Direction = void 0;
819(function (Direction) {
820 // (These are chosen to match the base levels, in bidi algorithm
821 // terms, of spans in that direction.)
822 /**
823 Left-to-right.
824 */
825 Direction[Direction["LTR"] = 0] = "LTR";
826 /**
827 Right-to-left.
828 */
829 Direction[Direction["RTL"] = 1] = "RTL";
830})(exports.Direction || (exports.Direction = {}));
831const LTR = exports.Direction.LTR, RTL = exports.Direction.RTL;
832// Decode a string with each type encoded as log2(type)
833function dec(str) {
834 let result = [];
835 for (let i = 0; i < str.length; i++)
836 result.push(1 << +str[i]);
837 return result;
838}
839// Character types for codepoints 0 to 0xf8
840const LowTypes = dec("88888888888888888888888888888888888666888888787833333333337888888000000000000000000000000008888880000000000000000000000000088888888888888888888888888888888888887866668888088888663380888308888800000000000000000000000800000000000000000000000000000008");
841// Character types for codepoints 0x600 to 0x6f9
842const ArabicTypes = dec("4444448826627288999999999992222222222222222222222222222222222222222222222229999999999999999999994444444444644222822222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222999999949999999229989999223333333333");
843const Brackets = Object.create(null), BracketStack = [];
844// There's a lot more in
845// https://www.unicode.org/Public/UCD/latest/ucd/BidiBrackets.txt,
846// which are left out to keep code size down.
847for (let p of ["()", "[]", "{}"]) {
848 let l = p.charCodeAt(0), r = p.charCodeAt(1);
849 Brackets[l] = r;
850 Brackets[r] = -l;
851}
852function charType(ch) {
853 return ch <= 0xf7 ? LowTypes[ch] :
854 0x590 <= ch && ch <= 0x5f4 ? 2 /* T.R */ :
855 0x600 <= ch && ch <= 0x6f9 ? ArabicTypes[ch - 0x600] :
856 0x6ee <= ch && ch <= 0x8ac ? 4 /* T.AL */ :
857 0x2000 <= ch && ch <= 0x200c ? 256 /* T.NI */ :
858 0xfb50 <= ch && ch <= 0xfdff ? 4 /* T.AL */ : 1 /* T.L */;
859}
860const BidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac\ufb50-\ufdff]/;
861/**
862Represents a contiguous range of text that has a single direction
863(as in left-to-right or right-to-left).
864*/
865class BidiSpan {
866 /**
867 The direction of this span.
868 */
869 get dir() { return this.level % 2 ? RTL : LTR; }
870 /**
871 @internal
872 */
873 constructor(
874 /**
875 The start of the span (relative to the start of the line).
876 */
877 from,
878 /**
879 The end of the span.
880 */
881 to,
882 /**
883 The ["bidi
884 level"](https://unicode.org/reports/tr9/#Basic_Display_Algorithm)
885 of the span (in this context, 0 means
886 left-to-right, 1 means right-to-left, 2 means left-to-right
887 number inside right-to-left text).
888 */
889 level) {
890 this.from = from;
891 this.to = to;
892 this.level = level;
893 }
894 /**
895 @internal
896 */
897 side(end, dir) { return (this.dir == dir) == end ? this.to : this.from; }
898 /**
899 @internal
900 */
901 forward(forward, dir) { return forward == (this.dir == dir); }
902 /**
903 @internal
904 */
905 static find(order, index, level, assoc) {
906 let maybe = -1;
907 for (let i = 0; i < order.length; i++) {
908 let span = order[i];
909 if (span.from <= index && span.to >= index) {
910 if (span.level == level)
911 return i;
912 // When multiple spans match, if assoc != 0, take the one that
913 // covers that side, otherwise take the one with the minimum
914 // level.
915 if (maybe < 0 || (assoc != 0 ? (assoc < 0 ? span.from < index : span.to > index) : order[maybe].level > span.level))
916 maybe = i;
917 }
918 }
919 if (maybe < 0)
920 throw new RangeError("Index out of range");
921 return maybe;
922 }
923}
924function isolatesEq(a, b) {
925 if (a.length != b.length)
926 return false;
927 for (let i = 0; i < a.length; i++) {
928 let iA = a[i], iB = b[i];
929 if (iA.from != iB.from || iA.to != iB.to || iA.direction != iB.direction || !isolatesEq(iA.inner, iB.inner))
930 return false;
931 }
932 return true;
933}
934// Reused array of character types
935const types = [];
936// Fill in the character types (in `types`) from `from` to `to` and
937// apply W normalization rules.
938function computeCharTypes(line, rFrom, rTo, isolates, outerType) {
939 for (let iI = 0; iI <= isolates.length; iI++) {
940 let from = iI ? isolates[iI - 1].to : rFrom, to = iI < isolates.length ? isolates[iI].from : rTo;
941 let prevType = iI ? 256 /* T.NI */ : outerType;
942 // W1. Examine each non-spacing mark (NSM) in the level run, and
943 // change the type of the NSM to the type of the previous
944 // character. If the NSM is at the start of the level run, it will
945 // get the type of sor.
946 // W2. Search backwards from each instance of a European number
947 // until the first strong type (R, L, AL, or sor) is found. If an
948 // AL is found, change the type of the European number to Arabic
949 // number.
950 // W3. Change all ALs to R.
951 // (Left after this: L, R, EN, AN, ET, CS, NI)
952 for (let i = from, prev = prevType, prevStrong = prevType; i < to; i++) {
953 let type = charType(line.charCodeAt(i));
954 if (type == 512 /* T.NSM */)
955 type = prev;
956 else if (type == 8 /* T.EN */ && prevStrong == 4 /* T.AL */)
957 type = 16 /* T.AN */;
958 types[i] = type == 4 /* T.AL */ ? 2 /* T.R */ : type;
959 if (type & 7 /* T.Strong */)
960 prevStrong = type;
961 prev = type;
962 }
963 // W5. A sequence of European terminators adjacent to European
964 // numbers changes to all European numbers.
965 // W6. Otherwise, separators and terminators change to Other
966 // Neutral.
967 // W7. Search backwards from each instance of a European number
968 // until the first strong type (R, L, or sor) is found. If an L is
969 // found, then change the type of the European number to L.
970 // (Left after this: L, R, EN+AN, NI)
971 for (let i = from, prev = prevType, prevStrong = prevType; i < to; i++) {
972 let type = types[i];
973 if (type == 128 /* T.CS */) {
974 if (i < to - 1 && prev == types[i + 1] && (prev & 24 /* T.Num */))
975 type = types[i] = prev;
976 else
977 types[i] = 256 /* T.NI */;
978 }
979 else if (type == 64 /* T.ET */) {
980 let end = i + 1;
981 while (end < to && types[end] == 64 /* T.ET */)
982 end++;
983 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 */;
984 for (let j = i; j < end; j++)
985 types[j] = replace;
986 i = end - 1;
987 }
988 else if (type == 8 /* T.EN */ && prevStrong == 1 /* T.L */) {
989 types[i] = 1 /* T.L */;
990 }
991 prev = type;
992 if (type & 7 /* T.Strong */)
993 prevStrong = type;
994 }
995 }
996}
997// Process brackets throughout a run sequence.
998function processBracketPairs(line, rFrom, rTo, isolates, outerType) {
999 let oppositeType = outerType == 1 /* T.L */ ? 2 /* T.R */ : 1 /* T.L */;
1000 for (let iI = 0, sI = 0, context = 0; iI <= isolates.length; iI++) {
1001 let from = iI ? isolates[iI - 1].to : rFrom, to = iI < isolates.length ? isolates[iI].from : rTo;
1002 // N0. Process bracket pairs in an isolating run sequence
1003 // sequentially in the logical order of the text positions of the
1004 // opening paired brackets using the logic given below. Within this
1005 // scope, bidirectional types EN and AN are treated as R.
1006 for (let i = from, ch, br, type; i < to; i++) {
1007 // Keeps [startIndex, type, strongSeen] triples for each open
1008 // bracket on BracketStack.
1009 if (br = Brackets[ch = line.charCodeAt(i)]) {
1010 if (br < 0) { // Closing bracket
1011 for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
1012 if (BracketStack[sJ + 1] == -br) {
1013 let flags = BracketStack[sJ + 2];
1014 let type = (flags & 2 /* Bracketed.EmbedInside */) ? outerType :
1015 !(flags & 4 /* Bracketed.OppositeInside */) ? 0 :
1016 (flags & 1 /* Bracketed.OppositeBefore */) ? oppositeType : outerType;
1017 if (type)
1018 types[i] = types[BracketStack[sJ]] = type;
1019 sI = sJ;
1020 break;
1021 }
1022 }
1023 }
1024 else if (BracketStack.length == 189 /* Bracketed.MaxDepth */) {
1025 break;
1026 }
1027 else {
1028 BracketStack[sI++] = i;
1029 BracketStack[sI++] = ch;
1030 BracketStack[sI++] = context;
1031 }
1032 }
1033 else if ((type = types[i]) == 2 /* T.R */ || type == 1 /* T.L */) {
1034 let embed = type == outerType;
1035 context = embed ? 0 : 1 /* Bracketed.OppositeBefore */;
1036 for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
1037 let cur = BracketStack[sJ + 2];
1038 if (cur & 2 /* Bracketed.EmbedInside */)
1039 break;
1040 if (embed) {
1041 BracketStack[sJ + 2] |= 2 /* Bracketed.EmbedInside */;
1042 }
1043 else {
1044 if (cur & 4 /* Bracketed.OppositeInside */)
1045 break;
1046 BracketStack[sJ + 2] |= 4 /* Bracketed.OppositeInside */;
1047 }
1048 }
1049 }
1050 }
1051 }
1052}
1053function processNeutrals(rFrom, rTo, isolates, outerType) {
1054 for (let iI = 0, prev = outerType; iI <= isolates.length; iI++) {
1055 let from = iI ? isolates[iI - 1].to : rFrom, to = iI < isolates.length ? isolates[iI].from : rTo;
1056 // N1. A sequence of neutrals takes the direction of the
1057 // surrounding strong text if the text on both sides has the same
1058 // direction. European and Arabic numbers act as if they were R in
1059 // terms of their influence on neutrals. Start-of-level-run (sor)
1060 // and end-of-level-run (eor) are used at level run boundaries.
1061 // N2. Any remaining neutrals take the embedding direction.
1062 // (Left after this: L, R, EN+AN)
1063 for (let i = from; i < to;) {
1064 let type = types[i];
1065 if (type == 256 /* T.NI */) {
1066 let end = i + 1;
1067 for (;;) {
1068 if (end == to) {
1069 if (iI == isolates.length)
1070 break;
1071 end = isolates[iI++].to;
1072 to = iI < isolates.length ? isolates[iI].from : rTo;
1073 }
1074 else if (types[end] == 256 /* T.NI */) {
1075 end++;
1076 }
1077 else {
1078 break;
1079 }
1080 }
1081 let beforeL = prev == 1 /* T.L */;
1082 let afterL = (end < rTo ? types[end] : outerType) == 1 /* T.L */;
1083 let replace = beforeL == afterL ? (beforeL ? 1 /* T.L */ : 2 /* T.R */) : outerType;
1084 for (let j = end, jI = iI, fromJ = jI ? isolates[jI - 1].to : rFrom; j > i;) {
1085 if (j == fromJ) {
1086 j = isolates[--jI].from;
1087 fromJ = jI ? isolates[jI - 1].to : rFrom;
1088 }
1089 types[--j] = replace;
1090 }
1091 i = end;
1092 }
1093 else {
1094 prev = type;
1095 i++;
1096 }
1097 }
1098 }
1099}
1100// Find the contiguous ranges of character types in a given range, and
1101// emit spans for them. Flip the order of the spans as appropriate
1102// based on the level, and call through to compute the spans for
1103// isolates at the proper point.
1104function emitSpans(line, from, to, level, baseLevel, isolates, order) {
1105 let ourType = level % 2 ? 2 /* T.R */ : 1 /* T.L */;
1106 if ((level % 2) == (baseLevel % 2)) { // Same dir as base direction, don't flip
1107 for (let iCh = from, iI = 0; iCh < to;) {
1108 // Scan a section of characters in direction ourType, unless
1109 // there's another type of char right after iCh, in which case
1110 // we scan a section of other characters (which, if ourType ==
1111 // T.L, may contain both T.R and T.AN chars).
1112 let sameDir = true, isNum = false;
1113 if (iI == isolates.length || iCh < isolates[iI].from) {
1114 let next = types[iCh];
1115 if (next != ourType) {
1116 sameDir = false;
1117 isNum = next == 16 /* T.AN */;
1118 }
1119 }
1120 // Holds an array of isolates to pass to a recursive call if we
1121 // must recurse (to distinguish T.AN inside an RTL section in
1122 // LTR text), null if we can emit directly
1123 let recurse = !sameDir && ourType == 1 /* T.L */ ? [] : null;
1124 let localLevel = sameDir ? level : level + 1;
1125 let iScan = iCh;
1126 run: for (;;) {
1127 if (iI < isolates.length && iScan == isolates[iI].from) {
1128 if (isNum)
1129 break run;
1130 let iso = isolates[iI];
1131 // Scan ahead to verify that there is another char in this dir after the isolate(s)
1132 if (!sameDir)
1133 for (let upto = iso.to, jI = iI + 1;;) {
1134 if (upto == to)
1135 break run;
1136 if (jI < isolates.length && isolates[jI].from == upto)
1137 upto = isolates[jI++].to;
1138 else if (types[upto] == ourType)
1139 break run;
1140 else
1141 break;
1142 }
1143 iI++;
1144 if (recurse) {
1145 recurse.push(iso);
1146 }
1147 else {
1148 if (iso.from > iCh)
1149 order.push(new BidiSpan(iCh, iso.from, localLevel));
1150 let dirSwap = (iso.direction == LTR) != !(localLevel % 2);
1151 computeSectionOrder(line, dirSwap ? level + 1 : level, baseLevel, iso.inner, iso.from, iso.to, order);
1152 iCh = iso.to;
1153 }
1154 iScan = iso.to;
1155 }
1156 else if (iScan == to || (sameDir ? types[iScan] != ourType : types[iScan] == ourType)) {
1157 break;
1158 }
1159 else {
1160 iScan++;
1161 }
1162 }
1163 if (recurse)
1164 emitSpans(line, iCh, iScan, level + 1, baseLevel, recurse, order);
1165 else if (iCh < iScan)
1166 order.push(new BidiSpan(iCh, iScan, localLevel));
1167 iCh = iScan;
1168 }
1169 }
1170 else {
1171 // Iterate in reverse to flip the span order. Same code again, but
1172 // going from the back of the section to the front
1173 for (let iCh = to, iI = isolates.length; iCh > from;) {
1174 let sameDir = true, isNum = false;
1175 if (!iI || iCh > isolates[iI - 1].to) {
1176 let next = types[iCh - 1];
1177 if (next != ourType) {
1178 sameDir = false;
1179 isNum = next == 16 /* T.AN */;
1180 }
1181 }
1182 let recurse = !sameDir && ourType == 1 /* T.L */ ? [] : null;
1183 let localLevel = sameDir ? level : level + 1;
1184 let iScan = iCh;
1185 run: for (;;) {
1186 if (iI && iScan == isolates[iI - 1].to) {
1187 if (isNum)
1188 break run;
1189 let iso = isolates[--iI];
1190 // Scan ahead to verify that there is another char in this dir after the isolate(s)
1191 if (!sameDir)
1192 for (let upto = iso.from, jI = iI;;) {
1193 if (upto == from)
1194 break run;
1195 if (jI && isolates[jI - 1].to == upto)
1196 upto = isolates[--jI].from;
1197 else if (types[upto - 1] == ourType)
1198 break run;
1199 else
1200 break;
1201 }
1202 if (recurse) {
1203 recurse.push(iso);
1204 }
1205 else {
1206 if (iso.to < iCh)
1207 order.push(new BidiSpan(iso.to, iCh, localLevel));
1208 let dirSwap = (iso.direction == LTR) != !(localLevel % 2);
1209 computeSectionOrder(line, dirSwap ? level + 1 : level, baseLevel, iso.inner, iso.from, iso.to, order);
1210 iCh = iso.from;
1211 }
1212 iScan = iso.from;
1213 }
1214 else if (iScan == from || (sameDir ? types[iScan - 1] != ourType : types[iScan - 1] == ourType)) {
1215 break;
1216 }
1217 else {
1218 iScan--;
1219 }
1220 }
1221 if (recurse)
1222 emitSpans(line, iScan, iCh, level + 1, baseLevel, recurse, order);
1223 else if (iScan < iCh)
1224 order.push(new BidiSpan(iScan, iCh, localLevel));
1225 iCh = iScan;
1226 }
1227 }
1228}
1229function computeSectionOrder(line, level, baseLevel, isolates, from, to, order) {
1230 let outerType = (level % 2 ? 2 /* T.R */ : 1 /* T.L */);
1231 computeCharTypes(line, from, to, isolates, outerType);
1232 processBracketPairs(line, from, to, isolates, outerType);
1233 processNeutrals(from, to, isolates, outerType);
1234 emitSpans(line, from, to, level, baseLevel, isolates, order);
1235}
1236function computeOrder(line, direction, isolates) {
1237 if (!line)
1238 return [new BidiSpan(0, 0, direction == RTL ? 1 : 0)];
1239 if (direction == LTR && !isolates.length && !BidiRE.test(line))
1240 return trivialOrder(line.length);
1241 if (isolates.length)
1242 while (line.length > types.length)
1243 types[types.length] = 256 /* T.NI */; // Make sure types array has no gaps
1244 let order = [], level = direction == LTR ? 0 : 1;
1245 computeSectionOrder(line, level, level, isolates, 0, line.length, order);
1246 return order;
1247}
1248function trivialOrder(length) {
1249 return [new BidiSpan(0, length, 0)];
1250}
1251let movedOver = "";
1252// This implementation moves strictly visually, without concern for a
1253// traversal visiting every logical position in the string. It will
1254// still do so for simple input, but situations like multiple isolates
1255// with the same level next to each other, or text going against the
1256// main dir at the end of the line, will make some positions
1257// unreachable with this motion. Each visible cursor position will
1258// correspond to the lower-level bidi span that touches it.
1259//
1260// The alternative would be to solve an order globally for a given
1261// line, making sure that it includes every position, but that would
1262// require associating non-canonical (higher bidi span level)
1263// positions with a given visual position, which is likely to confuse
1264// people. (And would generally be a lot more complicated.)
1265function moveVisually(line, order, dir, start, forward) {
1266 var _a;
1267 let startIndex = start.head - line.from;
1268 let spanI = BidiSpan.find(order, startIndex, (_a = start.bidiLevel) !== null && _a !== void 0 ? _a : -1, start.assoc);
1269 let span = order[spanI], spanEnd = span.side(forward, dir);
1270 // End of span
1271 if (startIndex == spanEnd) {
1272 let nextI = spanI += forward ? 1 : -1;
1273 if (nextI < 0 || nextI >= order.length)
1274 return null;
1275 span = order[spanI = nextI];
1276 startIndex = span.side(!forward, dir);
1277 spanEnd = span.side(forward, dir);
1278 }
1279 let nextIndex = state.findClusterBreak(line.text, startIndex, span.forward(forward, dir));
1280 if (nextIndex < span.from || nextIndex > span.to)
1281 nextIndex = spanEnd;
1282 movedOver = line.text.slice(Math.min(startIndex, nextIndex), Math.max(startIndex, nextIndex));
1283 let nextSpan = spanI == (forward ? order.length - 1 : 0) ? null : order[spanI + (forward ? 1 : -1)];
1284 if (nextSpan && nextIndex == spanEnd && nextSpan.level + (forward ? 0 : 1) < span.level)
1285 return state.EditorSelection.cursor(nextSpan.side(!forward, dir) + line.from, nextSpan.forward(forward, dir) ? 1 : -1, nextSpan.level);
1286 return state.EditorSelection.cursor(nextIndex + line.from, span.forward(forward, dir) ? -1 : 1, span.level);
1287}
1288function autoDirection(text, from, to) {
1289 for (let i = from; i < to; i++) {
1290 let type = charType(text.charCodeAt(i));
1291 if (type == 1 /* T.L */)
1292 return LTR;
1293 if (type == 2 /* T.R */ || type == 4 /* T.AL */)
1294 return RTL;
1295 }
1296 return LTR;
1297}
1298
1299const clickAddsSelectionRange = state.Facet.define();
1300const dragMovesSelection$1 = state.Facet.define();
1301const mouseSelectionStyle = state.Facet.define();
1302const exceptionSink = state.Facet.define();
1303const updateListener = state.Facet.define();
1304const inputHandler = state.Facet.define();
1305const focusChangeEffect = state.Facet.define();
1306const clipboardInputFilter = state.Facet.define();
1307const clipboardOutputFilter = state.Facet.define();
1308const perLineTextDirection = state.Facet.define({
1309 combine: values => values.some(x => x)
1310});
1311const nativeSelectionHidden = state.Facet.define({
1312 combine: values => values.some(x => x)
1313});
1314const scrollHandler = state.Facet.define();
1315class ScrollTarget {
1316 constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5,
1317 // This data structure is abused to also store precise scroll
1318 // snapshots, instead of a `scrollIntoView` request. When this
1319 // flag is `true`, `range` points at a position in the reference
1320 // line, `yMargin` holds the difference between the top of that
1321 // line and the top of the editor, and `xMargin` holds the
1322 // editor's `scrollLeft`.
1323 isSnapshot = false) {
1324 this.range = range;
1325 this.y = y;
1326 this.x = x;
1327 this.yMargin = yMargin;
1328 this.xMargin = xMargin;
1329 this.isSnapshot = isSnapshot;
1330 }
1331 map(changes) {
1332 return changes.empty ? this :
1333 new ScrollTarget(this.range.map(changes), this.y, this.x, this.yMargin, this.xMargin, this.isSnapshot);
1334 }
1335 clip(state$1) {
1336 return this.range.to <= state$1.doc.length ? this :
1337 new ScrollTarget(state.EditorSelection.cursor(state$1.doc.length), this.y, this.x, this.yMargin, this.xMargin, this.isSnapshot);
1338 }
1339}
1340const scrollIntoView = state.StateEffect.define({ map: (t, ch) => t.map(ch) });
1341const setEditContextFormatting = state.StateEffect.define();
1342/**
1343Log or report an unhandled exception in client code. Should
1344probably only be used by extension code that allows client code to
1345provide functions, and calls those functions in a context where an
1346exception can't be propagated to calling code in a reasonable way
1347(for example when in an event handler).
1348
1349Either calls a handler registered with
1350[`EditorView.exceptionSink`](https://codemirror.net/6/docs/ref/#view.EditorView^exceptionSink),
1351`window.onerror`, if defined, or `console.error` (in which case
1352it'll pass `context`, when given, as first argument).
1353*/
1354function logException(state, exception, context) {
1355 let handler = state.facet(exceptionSink);
1356 if (handler.length)
1357 handler[0](exception);
1358 else if (window.onerror && window.onerror(String(exception), context, undefined, undefined, exception)) ;
1359 else if (context)
1360 console.error(context + ":", exception);
1361 else
1362 console.error(exception);
1363}
1364const editable = state.Facet.define({ combine: values => values.length ? values[0] : true });
1365let nextPluginID = 0;
1366const viewPlugin = state.Facet.define({
1367 combine(plugins) {
1368 return plugins.filter((p, i) => {
1369 for (let j = 0; j < i; j++)
1370 if (plugins[j].plugin == p.plugin)
1371 return false;
1372 return true;
1373 });
1374 }
1375});
1376/**
1377View plugins associate stateful values with a view. They can
1378influence the way the content is drawn, and are notified of things
1379that happen in the view. They optionally take an argument, in
1380which case you need to call [`of`](https://codemirror.net/6/docs/ref/#view.ViewPlugin.of) to create
1381an extension for the plugin. When the argument type is undefined,
1382you can use the plugin instance as an extension directly.
1383*/
1384class ViewPlugin {
1385 constructor(
1386 /**
1387 @internal
1388 */
1389 id,
1390 /**
1391 @internal
1392 */
1393 create,
1394 /**
1395 @internal
1396 */
1397 domEventHandlers,
1398 /**
1399 @internal
1400 */
1401 domEventObservers, buildExtensions) {
1402 this.id = id;
1403 this.create = create;
1404 this.domEventHandlers = domEventHandlers;
1405 this.domEventObservers = domEventObservers;
1406 this.baseExtensions = buildExtensions(this);
1407 this.extension = this.baseExtensions.concat(viewPlugin.of({ plugin: this, arg: undefined }));
1408 }
1409 /**
1410 Create an extension for this plugin with the given argument.
1411 */
1412 of(arg) {
1413 return this.baseExtensions.concat(viewPlugin.of({ plugin: this, arg }));
1414 }
1415 /**
1416 Define a plugin from a constructor function that creates the
1417 plugin's value, given an editor view.
1418 */
1419 static define(create, spec) {
1420 const { eventHandlers, eventObservers, provide, decorations: deco } = spec || {};
1421 return new ViewPlugin(nextPluginID++, create, eventHandlers, eventObservers, plugin => {
1422 let ext = [];
1423 if (deco)
1424 ext.push(decorations.of(view => {
1425 let pluginInst = view.plugin(plugin);
1426 return pluginInst ? deco(pluginInst) : Decoration.none;
1427 }));
1428 if (provide)
1429 ext.push(provide(plugin));
1430 return ext;
1431 });
1432 }
1433 /**
1434 Create a plugin for a class whose constructor takes a single
1435 editor view as argument.
1436 */
1437 static fromClass(cls, spec) {
1438 return ViewPlugin.define((view, arg) => new cls(view, arg), spec);
1439 }
1440}
1441class PluginInstance {
1442 constructor(spec) {
1443 this.spec = spec;
1444 // When starting an update, all plugins have this field set to the
1445 // update object, indicating they need to be updated. When finished
1446 // updating, it is set to `null`. Retrieving a plugin that needs to
1447 // be updated with `view.plugin` forces an eager update.
1448 this.mustUpdate = null;
1449 // This is null when the plugin is initially created, but
1450 // initialized on the first update.
1451 this.value = null;
1452 }
1453 get plugin() { return this.spec && this.spec.plugin; }
1454 update(view) {
1455 if (!this.value) {
1456 if (this.spec) {
1457 try {
1458 this.value = this.spec.plugin.create(view, this.spec.arg);
1459 }
1460 catch (e) {
1461 logException(view.state, e, "CodeMirror plugin crashed");
1462 this.deactivate();
1463 }
1464 }
1465 }
1466 else if (this.mustUpdate) {
1467 let update = this.mustUpdate;
1468 this.mustUpdate = null;
1469 if (this.value.update) {
1470 try {
1471 this.value.update(update);
1472 }
1473 catch (e) {
1474 logException(update.state, e, "CodeMirror plugin crashed");
1475 if (this.value.destroy)
1476 try {
1477 this.value.destroy();
1478 }
1479 catch (_) { }
1480 this.deactivate();
1481 }
1482 }
1483 }
1484 return this;
1485 }
1486 destroy(view) {
1487 var _a;
1488 if ((_a = this.value) === null || _a === void 0 ? void 0 : _a.destroy) {
1489 try {
1490 this.value.destroy();
1491 }
1492 catch (e) {
1493 logException(view.state, e, "CodeMirror plugin crashed");
1494 }
1495 }
1496 }
1497 deactivate() {
1498 this.spec = this.value = null;
1499 }
1500}
1501const editorAttributes = state.Facet.define();
1502const contentAttributes = state.Facet.define();
1503// Provide decorations
1504const decorations = state.Facet.define();
1505const blockWrappers = state.Facet.define();
1506const outerDecorations = state.Facet.define();
1507const atomicRanges = state.Facet.define();
1508const bidiIsolatedRanges = state.Facet.define();
1509function getIsolatedRanges(view, line) {
1510 let isolates = view.state.facet(bidiIsolatedRanges);
1511 if (!isolates.length)
1512 return isolates;
1513 let sets = isolates.map(i => i instanceof Function ? i(view) : i);
1514 let result = [];
1515 state.RangeSet.spans(sets, line.from, line.to, {
1516 point() { },
1517 span(fromDoc, toDoc, active, open) {
1518 let from = fromDoc - line.from, to = toDoc - line.from;
1519 let level = result;
1520 for (let i = active.length - 1; i >= 0; i--, open--) {
1521 let direction = active[i].spec.bidiIsolate, update;
1522 if (direction == null)
1523 direction = autoDirection(line.text, from, to);
1524 if (open > 0 && level.length &&
1525 (update = level[level.length - 1]).to == from && update.direction == direction) {
1526 update.to = to;
1527 level = update.inner;
1528 }
1529 else {
1530 let add = { from, to, direction, inner: [] };
1531 level.push(add);
1532 level = add.inner;
1533 }
1534 }
1535 }
1536 });
1537 return result;
1538}
1539const scrollMargins = state.Facet.define();
1540function getScrollMargins(view) {
1541 let left = 0, right = 0, top = 0, bottom = 0;
1542 for (let source of view.state.facet(scrollMargins)) {
1543 let m = source(view);
1544 if (m) {
1545 if (m.left != null)
1546 left = Math.max(left, m.left);
1547 if (m.right != null)
1548 right = Math.max(right, m.right);
1549 if (m.top != null)
1550 top = Math.max(top, m.top);
1551 if (m.bottom != null)
1552 bottom = Math.max(bottom, m.bottom);
1553 }
1554 }
1555 return { left, right, top, bottom };
1556}
1557const styleModule = state.Facet.define();
1558class ChangedRange {
1559 constructor(fromA, toA, fromB, toB) {
1560 this.fromA = fromA;
1561 this.toA = toA;
1562 this.fromB = fromB;
1563 this.toB = toB;
1564 }
1565 join(other) {
1566 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));
1567 }
1568 addToSet(set) {
1569 let i = set.length, me = this;
1570 for (; i > 0; i--) {
1571 let range = set[i - 1];
1572 if (range.fromA > me.toA)
1573 continue;
1574 if (range.toA < me.fromA)
1575 break;
1576 me = me.join(range);
1577 set.splice(i - 1, 1);
1578 }
1579 set.splice(i, 0, me);
1580 return set;
1581 }
1582 // Extend a set to cover all the content in `ranges`, which is a
1583 // flat array with each pair of numbers representing fromB/toB
1584 // positions. These pairs are generated in unchanged ranges, so the
1585 // offset between doc A and doc B is the same for their start and
1586 // end points.
1587 static extendWithRanges(diff, ranges) {
1588 if (ranges.length == 0)
1589 return diff;
1590 let result = [];
1591 for (let dI = 0, rI = 0, off = 0;;) {
1592 let nextD = dI < diff.length ? diff[dI].fromB : 1e9;
1593 let nextR = rI < ranges.length ? ranges[rI] : 1e9;
1594 let fromB = Math.min(nextD, nextR);
1595 if (fromB == 1e9)
1596 break;
1597 let fromA = fromB + off, toB = fromB, toA = fromA;
1598 for (;;) {
1599 if (rI < ranges.length && ranges[rI] <= toB) {
1600 let end = ranges[rI + 1];
1601 rI += 2;
1602 toB = Math.max(toB, end);
1603 for (let i = dI; i < diff.length && diff[i].fromB <= toB; i++)
1604 off = diff[i].toA - diff[i].toB;
1605 toA = Math.max(toA, end + off);
1606 }
1607 else if (dI < diff.length && diff[dI].fromB <= toB) {
1608 let next = diff[dI++];
1609 toB = Math.max(toB, next.toB);
1610 toA = Math.max(toA, next.toA);
1611 off = next.toA - next.toB;
1612 }
1613 else {
1614 break;
1615 }
1616 }
1617 result.push(new ChangedRange(fromA, toA, fromB, toB));
1618 }
1619 return result;
1620 }
1621}
1622/**
1623View [plugins](https://codemirror.net/6/docs/ref/#view.ViewPlugin) are given instances of this
1624class, which describe what happened, whenever the view is updated.
1625*/
1626class ViewUpdate {
1627 constructor(
1628 /**
1629 The editor view that the update is associated with.
1630 */
1631 view,
1632 /**
1633 The new editor state.
1634 */
1635 state$1,
1636 /**
1637 The transactions involved in the update. May be empty.
1638 */
1639 transactions) {
1640 this.view = view;
1641 this.state = state$1;
1642 this.transactions = transactions;
1643 /**
1644 @internal
1645 */
1646 this.flags = 0;
1647 this.startState = view.state;
1648 this.changes = state.ChangeSet.empty(this.startState.doc.length);
1649 for (let tr of transactions)
1650 this.changes = this.changes.compose(tr.changes);
1651 let changedRanges = [];
1652 this.changes.iterChangedRanges((fromA, toA, fromB, toB) => changedRanges.push(new ChangedRange(fromA, toA, fromB, toB)));
1653 this.changedRanges = changedRanges;
1654 }
1655 /**
1656 @internal
1657 */
1658 static create(view, state, transactions) {
1659 return new ViewUpdate(view, state, transactions);
1660 }
1661 /**
1662 Tells you whether the [viewport](https://codemirror.net/6/docs/ref/#view.EditorView.viewport) or
1663 [visible ranges](https://codemirror.net/6/docs/ref/#view.EditorView.visibleRanges) changed in this
1664 update.
1665 */
1666 get viewportChanged() {
1667 return (this.flags & 4 /* UpdateFlag.Viewport */) > 0;
1668 }
1669 /**
1670 Returns true when
1671 [`viewportChanged`](https://codemirror.net/6/docs/ref/#view.ViewUpdate.viewportChanged) is true
1672 and the viewport change is not just the result of mapping it in
1673 response to document changes.
1674 */
1675 get viewportMoved() {
1676 return (this.flags & 8 /* UpdateFlag.ViewportMoved */) > 0;
1677 }
1678 /**
1679 Indicates whether the height of a block element in the editor
1680 changed in this update.
1681 */
1682 get heightChanged() {
1683 return (this.flags & 2 /* UpdateFlag.Height */) > 0;
1684 }
1685 /**
1686 Returns true when the document was modified or the size of the
1687 editor, or elements within the editor, changed.
1688 */
1689 get geometryChanged() {
1690 return this.docChanged || (this.flags & (16 /* UpdateFlag.Geometry */ | 2 /* UpdateFlag.Height */)) > 0;
1691 }
1692 /**
1693 True when this update indicates a focus change.
1694 */
1695 get focusChanged() {
1696 return (this.flags & 1 /* UpdateFlag.Focus */) > 0;
1697 }
1698 /**
1699 Whether the document changed in this update.
1700 */
1701 get docChanged() {
1702 return !this.changes.empty;
1703 }
1704 /**
1705 Whether the selection was explicitly set in this update.
1706 */
1707 get selectionSet() {
1708 return this.transactions.some(tr => tr.selection);
1709 }
1710 /**
1711 @internal
1712 */
1713 get empty() { return this.flags == 0 && this.transactions.length == 0; }
1714}
1715
1716const noChildren = [];
1717class Tile {
1718 constructor(dom, length, flags = 0) {
1719 this.dom = dom;
1720 this.length = length;
1721 this.flags = flags;
1722 this.parent = null;
1723 dom.cmTile = this;
1724 }
1725 get breakAfter() { return (this.flags & 1 /* TileFlag.BreakAfter */); }
1726 get children() { return noChildren; }
1727 isWidget() { return false; }
1728 get isHidden() { return false; }
1729 isComposite() { return false; }
1730 isLine() { return false; }
1731 isText() { return false; }
1732 isBlock() { return false; }
1733 get domAttrs() { return null; }
1734 sync(track) {
1735 this.flags |= 2 /* TileFlag.Synced */;
1736 if (this.flags & 4 /* TileFlag.AttrsDirty */) {
1737 this.flags &= ~4 /* TileFlag.AttrsDirty */;
1738 let attrs = this.domAttrs;
1739 if (attrs)
1740 setAttrs(this.dom, attrs);
1741 }
1742 }
1743 toString() {
1744 return this.constructor.name + (this.children.length ? `(${this.children})` : "") + (this.breakAfter ? "#" : "");
1745 }
1746 destroy() { this.parent = null; }
1747 setDOM(dom) {
1748 this.dom = dom;
1749 dom.cmTile = this;
1750 }
1751 get posAtStart() {
1752 return this.parent ? this.parent.posBefore(this) : 0;
1753 }
1754 get posAtEnd() {
1755 return this.posAtStart + this.length;
1756 }
1757 posBefore(tile, start = this.posAtStart) {
1758 let pos = start;
1759 for (let child of this.children) {
1760 if (child == tile)
1761 return pos;
1762 pos += child.length + child.breakAfter;
1763 }
1764 throw new RangeError("Invalid child in posBefore");
1765 }
1766 posAfter(tile) {
1767 return this.posBefore(tile) + tile.length;
1768 }
1769 covers(side) { return true; }
1770 coordsIn(pos, side) { return null; }
1771 domPosFor(off, side) {
1772 let index = domIndex(this.dom);
1773 let after = this.length ? off > 0 : side > 0;
1774 return new DOMPos(this.parent.dom, index + (after ? 1 : 0), off == 0 || off == this.length);
1775 }
1776 markDirty(attrs) {
1777 this.flags &= ~2 /* TileFlag.Synced */;
1778 if (attrs)
1779 this.flags |= 4 /* TileFlag.AttrsDirty */;
1780 if (this.parent && (this.parent.flags & 2 /* TileFlag.Synced */))
1781 this.parent.markDirty(false);
1782 }
1783 get overrideDOMText() { return null; }
1784 get root() {
1785 for (let t = this; t; t = t.parent)
1786 if (t instanceof DocTile)
1787 return t;
1788 return null;
1789 }
1790 static get(dom) {
1791 return dom.cmTile;
1792 }
1793}
1794class CompositeTile extends Tile {
1795 constructor(dom) {
1796 super(dom, 0);
1797 this._children = [];
1798 }
1799 isComposite() { return true; }
1800 get children() { return this._children; }
1801 get lastChild() { return this.children.length ? this.children[this.children.length - 1] : null; }
1802 append(child) {
1803 this.children.push(child);
1804 child.parent = this;
1805 }
1806 sync(track) {
1807 if (this.flags & 2 /* TileFlag.Synced */)
1808 return;
1809 super.sync(track);
1810 let parent = this.dom, prev = null, next;
1811 let tracking = (track === null || track === void 0 ? void 0 : track.node) == parent ? track : null;
1812 let length = 0;
1813 for (let child of this.children) {
1814 child.sync(track);
1815 length += child.length + child.breakAfter;
1816 next = prev ? prev.nextSibling : parent.firstChild;
1817 if (tracking && next != child.dom)
1818 tracking.written = true;
1819 if (child.dom.parentNode == parent) {
1820 while (next && next != child.dom)
1821 next = rm$1(next);
1822 }
1823 else {
1824 parent.insertBefore(child.dom, next);
1825 }
1826 prev = child.dom;
1827 }
1828 next = prev ? prev.nextSibling : parent.firstChild;
1829 if (tracking && next)
1830 tracking.written = true;
1831 while (next)
1832 next = rm$1(next);
1833 this.length = length;
1834 }
1835}
1836// Remove a DOM node and return its next sibling.
1837function rm$1(dom) {
1838 let next = dom.nextSibling;
1839 dom.parentNode.removeChild(dom);
1840 return next;
1841}
1842// The top-level tile. Its dom property equals view.contentDOM.
1843class DocTile extends CompositeTile {
1844 constructor(view, dom) {
1845 super(dom);
1846 this.view = view;
1847 }
1848 owns(tile) {
1849 for (; tile; tile = tile.parent)
1850 if (tile == this)
1851 return true;
1852 return false;
1853 }
1854 isBlock() { return true; }
1855 nearest(dom) {
1856 for (;;) {
1857 if (!dom)
1858 return null;
1859 let tile = Tile.get(dom);
1860 if (tile && this.owns(tile))
1861 return tile;
1862 dom = dom.parentNode;
1863 }
1864 }
1865 blockTiles(f) {
1866 for (let stack = [], cur = this, i = 0, pos = 0;;) {
1867 if (i == cur.children.length) {
1868 if (!stack.length)
1869 return;
1870 cur = cur.parent;
1871 if (cur.breakAfter)
1872 pos++;
1873 i = stack.pop();
1874 }
1875 else {
1876 let next = cur.children[i++];
1877 if (next instanceof BlockWrapperTile) {
1878 stack.push(i);
1879 cur = next;
1880 i = 0;
1881 }
1882 else {
1883 let end = pos + next.length;
1884 let result = f(next, pos);
1885 if (result !== undefined)
1886 return result;
1887 pos = end + next.breakAfter;
1888 }
1889 }
1890 }
1891 }
1892 // Find the block at the given position. If side < -1, make sure to
1893 // stay before block widgets at that position, if side > 1, after
1894 // such widgets (used for selection drawing, which needs to be able
1895 // to get coordinates for positions that aren't valid cursor positions).
1896 resolveBlock(pos, side) {
1897 let before, beforeOff = -1, after, afterOff = -1;
1898 this.blockTiles((tile, off) => {
1899 let end = off + tile.length;
1900 if (pos >= off && pos <= end) {
1901 if (tile.isWidget() && side >= -1 && side <= 1) {
1902 if (tile.flags & 32 /* TileFlag.After */)
1903 return true;
1904 if (tile.flags & 16 /* TileFlag.Before */)
1905 before = undefined;
1906 }
1907 if ((off < pos || pos == end && (side < -1 ? tile.length : tile.covers(1))) &&
1908 (!before || !tile.isWidget() && before.isWidget())) {
1909 before = tile;
1910 beforeOff = pos - off;
1911 }
1912 if ((end > pos || pos == off && (side > 1 ? tile.length : tile.covers(-1))) &&
1913 (!after || !tile.isWidget() && after.isWidget())) {
1914 after = tile;
1915 afterOff = pos - off;
1916 }
1917 }
1918 });
1919 if (!before && !after)
1920 throw new Error("No tile at position " + pos);
1921 return before && side < 0 || !after ? { tile: before, offset: beforeOff } : { tile: after, offset: afterOff };
1922 }
1923}
1924class BlockWrapperTile extends CompositeTile {
1925 constructor(dom, wrapper) {
1926 super(dom);
1927 this.wrapper = wrapper;
1928 }
1929 isBlock() { return true; }
1930 covers(side) {
1931 if (!this.children.length)
1932 return false;
1933 return side < 0 ? this.children[0].covers(-1) : this.lastChild.covers(1);
1934 }
1935 get domAttrs() { return this.wrapper.attributes; }
1936 static of(wrapper, dom) {
1937 let tile = new BlockWrapperTile(dom || document.createElement(wrapper.tagName), wrapper);
1938 if (!dom)
1939 tile.flags |= 4 /* TileFlag.AttrsDirty */;
1940 return tile;
1941 }
1942}
1943class LineTile extends CompositeTile {
1944 constructor(dom, attrs) {
1945 super(dom);
1946 this.attrs = attrs;
1947 }
1948 isLine() { return true; }
1949 static start(attrs, dom, keepAttrs) {
1950 let line = new LineTile(dom || document.createElement("div"), attrs);
1951 if (!dom || !keepAttrs)
1952 line.flags |= 4 /* TileFlag.AttrsDirty */;
1953 return line;
1954 }
1955 get domAttrs() { return this.attrs; }
1956 // Find the tile associated with a given position in this line.
1957 resolveInline(pos, side, forCoords) {
1958 let before = null, beforeOff = -1, after = null, afterOff = -1;
1959 function scan(tile, pos) {
1960 for (let i = 0, off = 0; i < tile.children.length && off <= pos; i++) {
1961 let child = tile.children[i], end = off + child.length;
1962 if (end >= pos) {
1963 if (child.isComposite()) {
1964 scan(child, pos - off);
1965 }
1966 else if ((!after || after.isHidden && (side > 0 || forCoords && onSameLine(after, child))) &&
1967 (end > pos || (child.flags & 32 /* TileFlag.After */))) {
1968 after = child;
1969 afterOff = pos - off;
1970 }
1971 else if (off < pos || (child.flags & 16 /* TileFlag.Before */) && !child.isHidden) {
1972 before = child;
1973 beforeOff = pos - off;
1974 }
1975 }
1976 off = end;
1977 }
1978 }
1979 scan(this, pos);
1980 let target = ((side < 0 ? before : after) || before || after);
1981 return target ? { tile: target, offset: target == before ? beforeOff : afterOff } : null;
1982 }
1983 coordsIn(pos, side) {
1984 let found = this.resolveInline(pos, side, true);
1985 if (!found)
1986 return fallbackRect(this);
1987 return found.tile.coordsIn(Math.max(0, found.offset), side);
1988 }
1989 domIn(pos, side) {
1990 let found = this.resolveInline(pos, side);
1991 if (found) {
1992 let { tile, offset } = found;
1993 if (this.dom.contains(tile.dom)) {
1994 if (tile.isText())
1995 return new DOMPos(tile.dom, Math.min(tile.dom.nodeValue.length, offset));
1996 return tile.domPosFor(offset, tile.flags & 16 /* TileFlag.Before */ ? 1 : tile.flags & 32 /* TileFlag.After */ ? -1 : side);
1997 }
1998 let parent = found.tile.parent, saw = false;
1999 for (let ch of parent.children) {
2000 if (saw)
2001 return new DOMPos(ch.dom, 0);
2002 if (ch == found.tile) {
2003 saw = true;
2004 }
2005 }
2006 }
2007 return new DOMPos(this.dom, 0);
2008 }
2009}
2010function fallbackRect(tile) {
2011 let last = tile.dom.lastChild;
2012 if (!last)
2013 return tile.dom.getBoundingClientRect();
2014 let rects = clientRectsFor(last);
2015 return rects[rects.length - 1] || null;
2016}
2017function onSameLine(a, b) {
2018 let posA = a.coordsIn(0, 1), posB = b.coordsIn(0, 1);
2019 return posA && posB && posB.top < posA.bottom;
2020}
2021class MarkTile extends CompositeTile {
2022 constructor(dom, mark) {
2023 super(dom);
2024 this.mark = mark;
2025 }
2026 get domAttrs() { return this.mark.attrs; }
2027 static of(mark, dom) {
2028 let tile = new MarkTile(dom || document.createElement(mark.tagName), mark);
2029 if (!dom)
2030 tile.flags |= 4 /* TileFlag.AttrsDirty */;
2031 return tile;
2032 }
2033}
2034class TextTile extends Tile {
2035 constructor(dom, text) {
2036 super(dom, text.length);
2037 this.text = text;
2038 }
2039 sync(track) {
2040 if (this.flags & 2 /* TileFlag.Synced */)
2041 return;
2042 super.sync(track);
2043 if (this.dom.nodeValue != this.text) {
2044 if (track && track.node == this.dom)
2045 track.written = true;
2046 this.dom.nodeValue = this.text;
2047 }
2048 }
2049 isText() { return true; }
2050 toString() { return JSON.stringify(this.text); }
2051 coordsIn(pos, side) {
2052 let length = this.dom.nodeValue.length;
2053 if (pos > length)
2054 pos = length;
2055 let from = pos, to = pos, flatten = 0;
2056 if (pos == 0 && side < 0 || pos == length && side >= 0) {
2057 if (!(browser.chrome || browser.gecko)) { // These browsers reliably return valid rectangles for empty ranges
2058 if (pos) {
2059 from--;
2060 flatten = 1;
2061 } // FIXME this is wrong in RTL text
2062 else if (to < length) {
2063 to++;
2064 flatten = -1;
2065 }
2066 }
2067 }
2068 else {
2069 if (side < 0)
2070 from--;
2071 else if (to < length)
2072 to++;
2073 }
2074 let rects = textRange(this.dom, from, to).getClientRects();
2075 if (!rects.length)
2076 return null;
2077 let rect = rects[(flatten ? flatten < 0 : side >= 0) ? 0 : rects.length - 1];
2078 if (browser.safari && !flatten && rect.width == 0)
2079 rect = Array.prototype.find.call(rects, r => r.width) || rect;
2080 return flatten ? flattenRect(rect, flatten < 0) : rect || null;
2081 }
2082 static of(text, dom) {
2083 let tile = new TextTile(dom || document.createTextNode(text), text);
2084 if (!dom)
2085 tile.flags |= 2 /* TileFlag.Synced */;
2086 return tile;
2087 }
2088}
2089class WidgetTile extends Tile {
2090 constructor(dom, length, widget, flags) {
2091 super(dom, length, flags);
2092 this.widget = widget;
2093 }
2094 isWidget() { return true; }
2095 get isHidden() { return this.widget.isHidden; }
2096 covers(side) {
2097 if (this.flags & 48 /* TileFlag.PointWidget */)
2098 return false;
2099 return (this.flags & (side < 0 ? 64 /* TileFlag.IncStart */ : 128 /* TileFlag.IncEnd */)) > 0;
2100 }
2101 coordsIn(pos, side) { return this.coordsInWidget(pos, side, false); }
2102 coordsInWidget(pos, side, block) {
2103 let custom = this.widget.coordsAt(this.dom, pos, side);
2104 if (custom)
2105 return custom;
2106 if (block) {
2107 return flattenRect(this.dom.getBoundingClientRect(), this.length ? pos == 0 : side <= 0);
2108 }
2109 else {
2110 let rects = this.dom.getClientRects(), rect = null;
2111 if (!rects.length)
2112 return null;
2113 let fromBack = (this.flags & 16 /* TileFlag.Before */) ? true : (this.flags & 32 /* TileFlag.After */) ? false : pos > 0;
2114 for (let i = fromBack ? rects.length - 1 : 0;; i += (fromBack ? -1 : 1)) {
2115 rect = rects[i];
2116 if (pos > 0 ? i == 0 : i == rects.length - 1 || rect.top < rect.bottom)
2117 break;
2118 }
2119 return flattenRect(rect, !fromBack);
2120 }
2121 }
2122 get overrideDOMText() {
2123 if (!this.length)
2124 return state.Text.empty;
2125 let { root } = this;
2126 if (!root)
2127 return state.Text.empty;
2128 let start = this.posAtStart;
2129 return root.view.state.doc.slice(start, start + this.length);
2130 }
2131 destroy() {
2132 super.destroy();
2133 this.widget.destroy(this.dom);
2134 }
2135 static of(widget, view, length, flags, dom) {
2136 if (!dom) {
2137 dom = widget.toDOM(view);
2138 if (!widget.editable)
2139 dom.contentEditable = "false";
2140 }
2141 return new WidgetTile(dom, length, widget, flags);
2142 }
2143}
2144// These are drawn around uneditable widgets to avoid a number of
2145// browser bugs that show up when the cursor is directly next to
2146// uneditable inline content.
2147class WidgetBufferTile extends Tile {
2148 constructor(flags) {
2149 let img = document.createElement("img");
2150 img.className = "cm-widgetBuffer";
2151 img.setAttribute("aria-hidden", "true");
2152 super(img, 0, flags);
2153 }
2154 get isHidden() { return true; }
2155 get overrideDOMText() { return state.Text.empty; }
2156 coordsIn(pos) { return this.dom.getBoundingClientRect(); }
2157}
2158// Represents a position in the tile tree.
2159class TilePointer {
2160 constructor(top) {
2161 this.index = 0;
2162 this.beforeBreak = false;
2163 this.parents = [];
2164 this.tile = top;
2165 }
2166 // Advance by the given distance. If side is -1, stop leaving or
2167 // entering tiles, or skipping zero-length tiles, once the distance
2168 // has been traversed. When side is 1, leave, enter, or skip
2169 // everything at the end position.
2170 advance(dist, side, walker) {
2171 let { tile, index, beforeBreak, parents } = this;
2172 while (dist || side > 0) {
2173 if (!tile.isComposite()) {
2174 if (index == tile.length) {
2175 beforeBreak = !!tile.breakAfter;
2176 ({ tile, index } = parents.pop());
2177 index++;
2178 }
2179 else if (!dist) {
2180 break;
2181 }
2182 else {
2183 let take = Math.min(dist, tile.length - index);
2184 if (walker)
2185 walker.skip(tile, index, index + take);
2186 dist -= take;
2187 index += take;
2188 }
2189 }
2190 else if (beforeBreak) {
2191 if (!dist)
2192 break;
2193 if (walker)
2194 walker.break();
2195 dist--;
2196 beforeBreak = false;
2197 }
2198 else if (index == tile.children.length) {
2199 if (!dist && !parents.length)
2200 break;
2201 if (walker)
2202 walker.leave(tile);
2203 beforeBreak = !!tile.breakAfter;
2204 ({ tile, index } = parents.pop());
2205 index++;
2206 }
2207 else {
2208 let next = tile.children[index], brk = next.breakAfter;
2209 if ((side > 0 ? next.length <= dist : next.length < dist) &&
2210 (!walker || walker.skip(next, 0, next.length) !== false || !next.isComposite)) {
2211 beforeBreak = !!brk;
2212 index++;
2213 dist -= next.length;
2214 }
2215 else {
2216 parents.push({ tile, index });
2217 tile = next;
2218 index = 0;
2219 if (walker && next.isComposite())
2220 walker.enter(next);
2221 }
2222 }
2223 }
2224 this.tile = tile;
2225 this.index = index;
2226 this.beforeBreak = beforeBreak;
2227 return this;
2228 }
2229 get root() { return (this.parents.length ? this.parents[0].tile : this.tile); }
2230}
2231
2232// Used to track open block wrappers
2233class OpenWrapper {
2234 constructor(from, to, wrapper, rank) {
2235 this.from = from;
2236 this.to = to;
2237 this.wrapper = wrapper;
2238 this.rank = rank;
2239 }
2240}
2241// This class builds up a new document tile using input from either
2242// iteration over the old tree or iteration over the document +
2243// decorations. The add* methods emit elements into the tile
2244// structure. To avoid awkward synchronization issues, marks and block
2245// wrappers are treated as belonging to to their content, rather than
2246// opened/closed independently.
2247//
2248// All composite tiles that are touched by changes are rebuilt,
2249// reusing as much of the old tree (either whole nodes or just DOM
2250// elements) as possible. The new tree is built without the Synced
2251// flag, and then synced (during which DOM parent/child relations are
2252// fixed up, text nodes filled in, and attributes added) in a second
2253// phase.
2254class TileBuilder {
2255 constructor(cache, root, blockWrappers) {
2256 this.cache = cache;
2257 this.root = root;
2258 this.blockWrappers = blockWrappers;
2259 this.curLine = null;
2260 this.lastBlock = null;
2261 this.afterWidget = null;
2262 this.pos = 0;
2263 this.wrappers = [];
2264 this.wrapperPos = 0;
2265 }
2266 addText(text, marks, openStart, tile) {
2267 var _a;
2268 this.flushBuffer();
2269 let parent = this.ensureMarks(marks, openStart);
2270 let prev = parent.lastChild;
2271 if (prev && prev.isText() && !(prev.flags & 8 /* TileFlag.Composition */) && prev.length + text.length < 512 /* C.Chunk */) {
2272 this.cache.reused.set(prev, 2 /* Reused.DOM */);
2273 let tile = parent.children[parent.children.length - 1] = new TextTile(prev.dom, prev.text + text);
2274 tile.parent = parent;
2275 }
2276 else {
2277 parent.append(tile || TextTile.of(text, (_a = this.cache.find(TextTile)) === null || _a === void 0 ? void 0 : _a.dom));
2278 }
2279 this.pos += text.length;
2280 this.afterWidget = null;
2281 }
2282 addComposition(composition, context) {
2283 let line = this.curLine;
2284 if (line.dom != context.line.dom) {
2285 line.setDOM(this.cache.reused.has(context.line) ? freeNode(context.line.dom) : context.line.dom);
2286 this.cache.reused.set(context.line, 2 /* Reused.DOM */);
2287 }
2288 let head = line;
2289 for (let i = context.marks.length - 1; i >= 0; i--) {
2290 let mark = context.marks[i];
2291 let last = head.lastChild;
2292 if (last instanceof MarkTile && last.mark.eq(mark.mark)) {
2293 if (last.dom != mark.dom)
2294 last.setDOM(freeNode(mark.dom));
2295 head = last;
2296 }
2297 else {
2298 if (this.cache.reused.get(mark)) {
2299 let tile = Tile.get(mark.dom);
2300 if (tile)
2301 tile.setDOM(freeNode(mark.dom));
2302 }
2303 let nw = MarkTile.of(mark.mark, mark.dom);
2304 head.append(nw);
2305 head = nw;
2306 }
2307 this.cache.reused.set(mark, 2 /* Reused.DOM */);
2308 }
2309 let oldTile = Tile.get(composition.text);
2310 if (oldTile)
2311 this.cache.reused.set(oldTile, 2 /* Reused.DOM */);
2312 let text = new TextTile(composition.text, composition.text.nodeValue);
2313 text.flags |= 8 /* TileFlag.Composition */;
2314 head.append(text);
2315 }
2316 addInlineWidget(widget, marks, openStart) {
2317 // Adjacent same-side-facing non-replacing widgets don't need buffers between them
2318 let noSpace = this.afterWidget && (widget.flags & 48 /* TileFlag.PointWidget */) &&
2319 (this.afterWidget.flags & 48 /* TileFlag.PointWidget */) == (widget.flags & 48 /* TileFlag.PointWidget */);
2320 if (!noSpace)
2321 this.flushBuffer();
2322 let parent = this.ensureMarks(marks, openStart);
2323 if (!noSpace && !(widget.flags & 16 /* TileFlag.Before */))
2324 parent.append(this.getBuffer(1));
2325 parent.append(widget);
2326 this.pos += widget.length;
2327 this.afterWidget = widget;
2328 }
2329 addMark(tile, marks, openStart) {
2330 this.flushBuffer();
2331 let parent = this.ensureMarks(marks, openStart);
2332 parent.append(tile);
2333 this.pos += tile.length;
2334 this.afterWidget = null;
2335 }
2336 addBlockWidget(widget) {
2337 this.getBlockPos().append(widget);
2338 this.pos += widget.length;
2339 this.lastBlock = widget;
2340 this.endLine();
2341 }
2342 continueWidget(length) {
2343 let widget = this.afterWidget || this.lastBlock;
2344 widget.length += length;
2345 this.pos += length;
2346 }
2347 addLineStart(attrs, dom) {
2348 var _a;
2349 if (!attrs)
2350 attrs = lineBaseAttrs;
2351 let tile = LineTile.start(attrs, dom || ((_a = this.cache.find(LineTile)) === null || _a === void 0 ? void 0 : _a.dom), !!dom);
2352 this.getBlockPos().append(this.lastBlock = this.curLine = tile);
2353 }
2354 addLine(tile) {
2355 this.getBlockPos().append(tile);
2356 this.pos += tile.length;
2357 this.lastBlock = tile;
2358 this.endLine();
2359 }
2360 addBreak() {
2361 this.lastBlock.flags |= 1 /* TileFlag.BreakAfter */;
2362 this.endLine();
2363 this.pos++;
2364 }
2365 addLineStartIfNotCovered(attrs) {
2366 if (!this.blockPosCovered())
2367 this.addLineStart(attrs);
2368 }
2369 ensureLine(attrs) {
2370 if (!this.curLine)
2371 this.addLineStart(attrs);
2372 }
2373 ensureMarks(marks, openStart) {
2374 var _a;
2375 let parent = this.curLine;
2376 for (let i = marks.length - 1; i >= 0; i--) {
2377 let mark = marks[i], last;
2378 if (openStart > 0 && (last = parent.lastChild) && last instanceof MarkTile && last.mark.eq(mark)) {
2379 parent = last;
2380 openStart--;
2381 }
2382 else {
2383 let tile = MarkTile.of(mark, (_a = this.cache.find(MarkTile, m => m.mark.eq(mark))) === null || _a === void 0 ? void 0 : _a.dom);
2384 parent.append(tile);
2385 parent = tile;
2386 openStart = 0;
2387 }
2388 }
2389 return parent;
2390 }
2391 endLine() {
2392 if (this.curLine) {
2393 this.flushBuffer();
2394 let last = this.curLine.lastChild;
2395 if (!last || !hasContent(this.curLine, false) ||
2396 last.dom.nodeName != "BR" && last.isWidget() && !(browser.ios && hasContent(this.curLine, true)))
2397 this.curLine.append(this.cache.findWidget(BreakWidget, 0, 32 /* TileFlag.After */) ||
2398 new WidgetTile(BreakWidget.toDOM(), 0, BreakWidget, 32 /* TileFlag.After */));
2399 this.curLine = this.afterWidget = null;
2400 }
2401 }
2402 updateBlockWrappers() {
2403 if (this.wrapperPos > this.pos + 10000 /* C.WrapperReset */) {
2404 this.blockWrappers.goto(this.pos);
2405 this.wrappers.length = 0;
2406 }
2407 for (let i = this.wrappers.length - 1; i >= 0; i--)
2408 if (this.wrappers[i].to < this.pos)
2409 this.wrappers.splice(i, 1);
2410 for (let cur = this.blockWrappers; cur.value && cur.from <= this.pos; cur.next())
2411 if (cur.to >= this.pos) {
2412 let wrap = new OpenWrapper(cur.from, cur.to, cur.value, cur.rank), i = this.wrappers.length;
2413 while (i > 0 && (this.wrappers[i - 1].rank - wrap.rank || this.wrappers[i - 1].to - wrap.to) < 0)
2414 i--;
2415 this.wrappers.splice(i, 0, wrap);
2416 }
2417 this.wrapperPos = this.pos;
2418 }
2419 getBlockPos() {
2420 var _a;
2421 this.updateBlockWrappers();
2422 let parent = this.root;
2423 for (let wrap of this.wrappers) {
2424 let last = parent.lastChild;
2425 if (wrap.from < this.pos && last instanceof BlockWrapperTile && last.wrapper.eq(wrap.wrapper)) {
2426 parent = last;
2427 }
2428 else {
2429 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);
2430 parent.append(tile);
2431 parent = tile;
2432 }
2433 }
2434 return parent;
2435 }
2436 blockPosCovered() {
2437 let last = this.lastBlock;
2438 return last != null && !last.breakAfter && (!last.isWidget() || (last.flags & (32 /* TileFlag.After */ | 128 /* TileFlag.IncEnd */)) > 0);
2439 }
2440 getBuffer(side) {
2441 let flags = 2 /* TileFlag.Synced */ | (side < 0 ? 16 /* TileFlag.Before */ : 32 /* TileFlag.After */);
2442 let found = this.cache.find(WidgetBufferTile, undefined, 1 /* Reused.Full */);
2443 if (found)
2444 found.flags = flags;
2445 return found || new WidgetBufferTile(flags);
2446 }
2447 flushBuffer() {
2448 if (this.afterWidget && !(this.afterWidget.flags & 32 /* TileFlag.After */)) {
2449 this.afterWidget.parent.append(this.getBuffer(-1));
2450 this.afterWidget = null;
2451 }
2452 }
2453}
2454// Helps getting efficient access to the document text.
2455class TextStream {
2456 constructor(doc) {
2457 this.skipCount = 0;
2458 this.text = "";
2459 this.textOff = 0;
2460 this.cursor = doc.iter();
2461 }
2462 skip(len) {
2463 // Advance the iterator past the replaced content
2464 if (this.textOff + len <= this.text.length) {
2465 this.textOff += len;
2466 }
2467 else {
2468 this.skipCount += len - (this.text.length - this.textOff);
2469 this.text = "";
2470 this.textOff = 0;
2471 }
2472 }
2473 next(maxLen) {
2474 if (this.textOff == this.text.length) {
2475 let { value, lineBreak, done } = this.cursor.next(this.skipCount);
2476 this.skipCount = 0;
2477 if (done)
2478 throw new Error("Ran out of text content when drawing inline views");
2479 this.text = value;
2480 let len = this.textOff = Math.min(maxLen, value.length);
2481 return lineBreak ? null : value.slice(0, len);
2482 }
2483 let end = Math.min(this.text.length, this.textOff + maxLen);
2484 let chars = this.text.slice(this.textOff, end);
2485 this.textOff = end;
2486 return chars;
2487 }
2488}
2489// Assign the tile classes bucket numbers for caching.
2490const buckets = [WidgetTile, LineTile, TextTile, MarkTile, WidgetBufferTile, BlockWrapperTile, DocTile];
2491for (let i = 0; i < buckets.length; i++)
2492 buckets[i].bucket = i;
2493// Leaf tiles and line tiles may be reused in their entirety. All
2494// others will get new tiles allocated, using the old DOM when
2495// possible.
2496class TileCache {
2497 constructor(view) {
2498 this.view = view;
2499 // Buckets are circular buffers, using `index` as the current
2500 // position.
2501 this.buckets = buckets.map(() => []);
2502 this.index = buckets.map(() => 0);
2503 this.reused = new Map;
2504 }
2505 // Put a tile in the cache.
2506 add(tile) {
2507 let i = tile.constructor.bucket, bucket = this.buckets[i];
2508 if (bucket.length < 6 /* C.Bucket */)
2509 bucket.push(tile);
2510 else
2511 bucket[this.index[i] = (this.index[i] + 1) % 6 /* C.Bucket */] = tile;
2512 }
2513 find(cls, test, type = 2 /* Reused.DOM */) {
2514 let i = cls.bucket;
2515 let bucket = this.buckets[i], off = this.index[i];
2516 for (let j = bucket.length - 1; j >= 0; j--) {
2517 // Look at the most recently added items first (last-in, first-out)
2518 let index = (j + off) % bucket.length, tile = bucket[index];
2519 if ((!test || test(tile)) && !this.reused.has(tile)) {
2520 bucket.splice(index, 1);
2521 if (index < off)
2522 this.index[i]--;
2523 this.reused.set(tile, type);
2524 return tile;
2525 }
2526 }
2527 return null;
2528 }
2529 findWidget(widget, length, flags) {
2530 let widgets = this.buckets[0];
2531 if (widgets.length)
2532 for (let i = 0, pass = 0;; i++) {
2533 if (i == widgets.length) {
2534 if (pass)
2535 return null;
2536 pass = 1;
2537 i = 0;
2538 }
2539 let tile = widgets[i];
2540 if (!this.reused.has(tile) &&
2541 (pass == 0 ? tile.widget.compare(widget)
2542 : tile.widget.constructor == widget.constructor && widget.updateDOM(tile.dom, this.view))) {
2543 widgets.splice(i, 1);
2544 if (i < this.index[0])
2545 this.index[0]--;
2546 if (tile.widget == widget && tile.length == length && (tile.flags & (496 /* TileFlag.Widget */ | 1 /* TileFlag.BreakAfter */)) == flags) {
2547 this.reused.set(tile, 1 /* Reused.Full */);
2548 return tile;
2549 }
2550 else {
2551 this.reused.set(tile, 2 /* Reused.DOM */);
2552 return new WidgetTile(tile.dom, length, widget, (tile.flags & ~(496 /* TileFlag.Widget */ | 1 /* TileFlag.BreakAfter */)) | flags);
2553 }
2554 }
2555 }
2556 }
2557 reuse(tile) {
2558 this.reused.set(tile, 1 /* Reused.Full */);
2559 return tile;
2560 }
2561 maybeReuse(tile, type = 2 /* Reused.DOM */) {
2562 if (this.reused.has(tile))
2563 return undefined;
2564 this.reused.set(tile, type);
2565 return tile.dom;
2566 }
2567 clear() {
2568 for (let i = 0; i < this.buckets.length; i++)
2569 this.buckets[i].length = this.index[i] = 0;
2570 }
2571}
2572// This class organizes a pass over the document, guided by the array
2573// of replaced ranges. For ranges that haven't changed, it iterates
2574// the old tree and copies its content into the new document. For
2575// changed ranges, it runs a decoration iterator to guide generation
2576// of content.
2577class TileUpdate {
2578 constructor(view, old, blockWrappers, decorations, disallowBlockEffectsFor) {
2579 this.view = view;
2580 this.decorations = decorations;
2581 this.disallowBlockEffectsFor = disallowBlockEffectsFor;
2582 this.openWidget = false;
2583 this.openMarks = 0;
2584 this.cache = new TileCache(view);
2585 this.text = new TextStream(view.state.doc);
2586 this.builder = new TileBuilder(this.cache, new DocTile(view, view.contentDOM), state.RangeSet.iter(blockWrappers));
2587 this.cache.reused.set(old, 2 /* Reused.DOM */);
2588 this.old = new TilePointer(old);
2589 this.reuseWalker = {
2590 skip: (tile, from, to) => {
2591 this.cache.add(tile);
2592 if (tile.isComposite())
2593 return false;
2594 },
2595 enter: tile => this.cache.add(tile),
2596 leave: () => { },
2597 break: () => { }
2598 };
2599 }
2600 run(changes, composition) {
2601 let compositionContext = composition && this.getCompositionContext(composition.text);
2602 for (let posA = 0, posB = 0, i = 0;;) {
2603 let next = i < changes.length ? changes[i++] : null;
2604 let skipA = next ? next.fromA : this.old.root.length;
2605 if (skipA > posA) {
2606 let len = skipA - posA;
2607 this.preserve(len, !i, !next);
2608 posA = skipA;
2609 posB += len;
2610 }
2611 if (!next)
2612 break;
2613 // Compositions need to be handled specially, forcing the
2614 // focused text node and its parent nodes to remain stable at
2615 // that point in the document.
2616 if (composition && next.fromA <= composition.range.fromA && next.toA >= composition.range.toA) {
2617 this.forward(next.fromA, composition.range.fromA, composition.range.fromA < composition.range.toA ? 1 : -1);
2618 this.emit(posB, composition.range.fromB);
2619 this.cache.clear(); // Must not reuse DOM across composition
2620 this.builder.addComposition(composition, compositionContext);
2621 this.text.skip(composition.range.toB - composition.range.fromB);
2622 this.forward(composition.range.fromA, next.toA);
2623 this.emit(composition.range.toB, next.toB);
2624 }
2625 else {
2626 this.forward(next.fromA, next.toA);
2627 this.emit(posB, next.toB);
2628 }
2629 posB = next.toB;
2630 posA = next.toA;
2631 }
2632 if (this.builder.curLine)
2633 this.builder.endLine();
2634 return this.builder.root;
2635 }
2636 preserve(length, incStart, incEnd) {
2637 let activeMarks = getMarks(this.old), openMarks = this.openMarks;
2638 this.old.advance(length, incEnd ? 1 : -1, {
2639 skip: (tile, from, to) => {
2640 if (tile.isWidget()) {
2641 if (this.openWidget) {
2642 this.builder.continueWidget(to - from);
2643 }
2644 else {
2645 let widget = to > 0 || from < tile.length
2646 ? WidgetTile.of(tile.widget, this.view, to - from, tile.flags & 496 /* TileFlag.Widget */, this.cache.maybeReuse(tile))
2647 : this.cache.reuse(tile);
2648 if (widget.flags & 256 /* TileFlag.Block */) {
2649 widget.flags &= ~1 /* TileFlag.BreakAfter */;
2650 this.builder.addBlockWidget(widget);
2651 }
2652 else {
2653 this.builder.ensureLine(null);
2654 this.builder.addInlineWidget(widget, activeMarks, openMarks);
2655 openMarks = activeMarks.length;
2656 }
2657 }
2658 }
2659 else if (tile.isText()) {
2660 this.builder.ensureLine(null);
2661 if (!from && to == tile.length && !this.cache.reused.has(tile)) {
2662 this.builder.addText(tile.text, activeMarks, openMarks, this.cache.reuse(tile));
2663 }
2664 else {
2665 this.cache.add(tile);
2666 this.builder.addText(tile.text.slice(from, to), activeMarks, openMarks);
2667 }
2668 openMarks = activeMarks.length;
2669 }
2670 else if (tile.isLine()) {
2671 tile.flags &= ~1 /* TileFlag.BreakAfter */;
2672 this.cache.reused.set(tile, 1 /* Reused.Full */);
2673 this.builder.addLine(tile);
2674 }
2675 else if (tile instanceof WidgetBufferTile) {
2676 this.cache.add(tile);
2677 }
2678 else if (tile instanceof MarkTile) {
2679 this.builder.ensureLine(null);
2680 this.builder.addMark(tile, activeMarks, openMarks);
2681 this.cache.reused.set(tile, 1 /* Reused.Full */);
2682 openMarks = activeMarks.length;
2683 }
2684 else {
2685 return false;
2686 }
2687 this.openWidget = false;
2688 },
2689 enter: (tile) => {
2690 if (tile.isLine()) {
2691 this.builder.addLineStart(tile.attrs, this.cache.maybeReuse(tile));
2692 }
2693 else {
2694 this.cache.add(tile);
2695 if (tile instanceof MarkTile)
2696 activeMarks.unshift(tile.mark);
2697 }
2698 this.openWidget = false;
2699 },
2700 leave: (tile) => {
2701 if (tile.isLine()) {
2702 if (activeMarks.length)
2703 activeMarks.length = openMarks = 0;
2704 }
2705 else if (tile instanceof MarkTile) {
2706 activeMarks.shift();
2707 openMarks = Math.min(openMarks, activeMarks.length);
2708 }
2709 },
2710 break: () => {
2711 this.builder.addBreak();
2712 this.openWidget = false;
2713 },
2714 });
2715 this.text.skip(length);
2716 }
2717 emit(from, to) {
2718 let pendingLineAttrs = null;
2719 let b = this.builder, markCount = 0;
2720 let openEnd = state.RangeSet.spans(this.decorations, from, to, {
2721 point: (from, to, deco, active, openStart, index) => {
2722 if (deco instanceof PointDecoration) {
2723 if (this.disallowBlockEffectsFor[index]) {
2724 if (deco.block)
2725 throw new RangeError("Block decorations may not be specified via plugins");
2726 if (to > this.view.state.doc.lineAt(from).to)
2727 throw new RangeError("Decorations that replace line breaks may not be specified via plugins");
2728 }
2729 markCount = active.length;
2730 if (openStart > active.length) {
2731 b.continueWidget(to - from);
2732 }
2733 else {
2734 let widget = deco.widget || (deco.block ? NullWidget.block : NullWidget.inline);
2735 let flags = widgetFlags(deco);
2736 let tile = this.cache.findWidget(widget, to - from, flags) || WidgetTile.of(widget, this.view, to - from, flags);
2737 if (deco.block) {
2738 if (deco.startSide > 0)
2739 b.addLineStartIfNotCovered(pendingLineAttrs);
2740 b.addBlockWidget(tile);
2741 }
2742 else {
2743 b.ensureLine(pendingLineAttrs);
2744 b.addInlineWidget(tile, active, openStart);
2745 }
2746 }
2747 pendingLineAttrs = null;
2748 }
2749 else {
2750 pendingLineAttrs = addLineDeco(pendingLineAttrs, deco);
2751 }
2752 if (to > from)
2753 this.text.skip(to - from);
2754 },
2755 span: (from, to, active, openStart) => {
2756 for (let pos = from; pos < to;) {
2757 let chars = this.text.next(Math.min(512 /* C.Chunk */, to - pos));
2758 if (chars == null) { // Line break
2759 b.addLineStartIfNotCovered(pendingLineAttrs);
2760 b.addBreak();
2761 pos++;
2762 }
2763 else {
2764 b.ensureLine(pendingLineAttrs);
2765 b.addText(chars, active, pos == from ? openStart : active.length);
2766 pos += chars.length;
2767 }
2768 pendingLineAttrs = null;
2769 }
2770 }
2771 });
2772 b.addLineStartIfNotCovered(pendingLineAttrs);
2773 this.openWidget = openEnd > markCount;
2774 this.openMarks = openEnd;
2775 }
2776 forward(from, to, side = 1) {
2777 if (to - from <= 10) {
2778 this.old.advance(to - from, side, this.reuseWalker);
2779 }
2780 else {
2781 this.old.advance(5, -1, this.reuseWalker);
2782 this.old.advance(to - from - 10, -1);
2783 this.old.advance(5, side, this.reuseWalker);
2784 }
2785 }
2786 getCompositionContext(text) {
2787 let marks = [], line = null;
2788 for (let parent = text.parentNode;; parent = parent.parentNode) {
2789 let tile = Tile.get(parent);
2790 if (parent == this.view.contentDOM)
2791 break;
2792 if (tile instanceof MarkTile)
2793 marks.push(tile);
2794 else if (tile === null || tile === void 0 ? void 0 : tile.isLine())
2795 line = tile;
2796 else if (tile instanceof BlockWrapperTile) ; // Ignore
2797 else if (parent.nodeName == "DIV" && !line && parent != this.view.contentDOM)
2798 line = new LineTile(parent, lineBaseAttrs);
2799 else if (!line)
2800 marks.push(MarkTile.of(new MarkDecoration({ tagName: parent.nodeName.toLowerCase(), attributes: getAttrs(parent) }), parent));
2801 }
2802 return { line: line, marks };
2803 }
2804}
2805function hasContent(tile, requireText) {
2806 let scan = (tile) => {
2807 for (let ch of tile.children)
2808 if ((requireText ? ch.isText() : ch.length) || scan(ch))
2809 return true;
2810 return false;
2811 };
2812 return scan(tile);
2813}
2814function widgetFlags(deco) {
2815 let flags = deco.isReplace ? (deco.startSide < 0 ? 64 /* TileFlag.IncStart */ : 0) | (deco.endSide > 0 ? 128 /* TileFlag.IncEnd */ : 0)
2816 : (deco.startSide > 0 ? 32 /* TileFlag.After */ : 16 /* TileFlag.Before */);
2817 if (deco.block)
2818 flags |= 256 /* TileFlag.Block */;
2819 return flags;
2820}
2821const lineBaseAttrs = { class: "cm-line" };
2822function addLineDeco(value, deco) {
2823 let attrs = deco.spec.attributes, cls = deco.spec.class;
2824 if (!attrs && !cls)
2825 return value;
2826 if (!value)
2827 value = { class: "cm-line" };
2828 if (attrs)
2829 combineAttrs(attrs, value);
2830 if (cls)
2831 value.class += " " + cls;
2832 return value;
2833}
2834function getMarks(ptr) {
2835 let found = [];
2836 for (let i = ptr.parents.length; i > 1; i--) {
2837 let tile = i == ptr.parents.length ? ptr.tile : ptr.parents[i].tile;
2838 if (tile instanceof MarkTile)
2839 found.push(tile.mark);
2840 }
2841 return found;
2842}
2843function freeNode(node) {
2844 let tile = Tile.get(node);
2845 if (tile)
2846 tile.setDOM(node.cloneNode());
2847 return node;
2848}
2849class NullWidget extends WidgetType {
2850 constructor(tag) {
2851 super();
2852 this.tag = tag;
2853 }
2854 eq(other) { return other.tag == this.tag; }
2855 toDOM() { return document.createElement(this.tag); }
2856 updateDOM(elt) { return elt.nodeName.toLowerCase() == this.tag; }
2857 get isHidden() { return true; }
2858}
2859NullWidget.inline = new NullWidget("span");
2860NullWidget.block = new NullWidget("div");
2861const BreakWidget = new class extends WidgetType {
2862 toDOM() { return document.createElement("br"); }
2863 get isHidden() { return true; }
2864 get editable() { return true; }
2865};
2866
2867class DocView {
2868 constructor(view) {
2869 this.view = view;
2870 this.decorations = [];
2871 this.blockWrappers = [];
2872 this.dynamicDecorationMap = [false];
2873 this.domChanged = null;
2874 this.hasComposition = null;
2875 this.editContextFormatting = Decoration.none;
2876 this.lastCompositionAfterCursor = false;
2877 // Track a minimum width for the editor. When measuring sizes in
2878 // measureVisibleLineHeights, this is updated to point at the width
2879 // of a given element and its extent in the document. When a change
2880 // happens in that range, these are reset. That way, once we've seen
2881 // a line/element of a given length, we keep the editor wide enough
2882 // to fit at least that element, until it is changed, at which point
2883 // we forget it again.
2884 this.minWidth = 0;
2885 this.minWidthFrom = 0;
2886 this.minWidthTo = 0;
2887 // Track whether the DOM selection was set in a lossy way, so that
2888 // we don't mess it up when reading it back it
2889 this.impreciseAnchor = null;
2890 this.impreciseHead = null;
2891 this.forceSelection = false;
2892 // Used by the resize observer to ignore resizes that we caused
2893 // ourselves
2894 this.lastUpdate = Date.now();
2895 this.updateDeco();
2896 this.tile = new DocTile(view, view.contentDOM);
2897 this.updateInner([new ChangedRange(0, 0, 0, view.state.doc.length)], null);
2898 }
2899 // Update the document view to a given state.
2900 update(update) {
2901 var _a;
2902 let changedRanges = update.changedRanges;
2903 if (this.minWidth > 0 && changedRanges.length) {
2904 if (!changedRanges.every(({ fromA, toA }) => toA < this.minWidthFrom || fromA > this.minWidthTo)) {
2905 this.minWidth = this.minWidthFrom = this.minWidthTo = 0;
2906 }
2907 else {
2908 this.minWidthFrom = update.changes.mapPos(this.minWidthFrom, 1);
2909 this.minWidthTo = update.changes.mapPos(this.minWidthTo, 1);
2910 }
2911 }
2912 this.updateEditContextFormatting(update);
2913 let readCompositionAt = -1;
2914 if (this.view.inputState.composing >= 0 && !this.view.observer.editContext) {
2915 if ((_a = this.domChanged) === null || _a === void 0 ? void 0 : _a.newSel)
2916 readCompositionAt = this.domChanged.newSel.head;
2917 else if (!touchesComposition(update.changes, this.hasComposition) && !update.selectionSet)
2918 readCompositionAt = update.state.selection.main.head;
2919 }
2920 let composition = readCompositionAt > -1 ? findCompositionRange(this.view, update.changes, readCompositionAt) : null;
2921 this.domChanged = null;
2922 if (this.hasComposition) {
2923 let { from, to } = this.hasComposition;
2924 changedRanges = new ChangedRange(from, to, update.changes.mapPos(from, -1), update.changes.mapPos(to, 1))
2925 .addToSet(changedRanges.slice());
2926 }
2927 this.hasComposition = composition ? { from: composition.range.fromB, to: composition.range.toB } : null;
2928 // When the DOM nodes around the selection are moved to another
2929 // parent, Chrome sometimes reports a different selection through
2930 // getSelection than the one that it actually shows to the user.
2931 // This forces a selection update when lines are joined to work
2932 // around that. Issue #54
2933 if ((browser.ie || browser.chrome) && !composition && update &&
2934 update.state.doc.lines != update.startState.doc.lines)
2935 this.forceSelection = true;
2936 let prevDeco = this.decorations, prevWrappers = this.blockWrappers;
2937 this.updateDeco();
2938 let decoDiff = findChangedDeco(prevDeco, this.decorations, update.changes);
2939 if (decoDiff.length)
2940 changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
2941 let blockDiff = findChangedWrappers(prevWrappers, this.blockWrappers, update.changes);
2942 if (blockDiff.length)
2943 changedRanges = ChangedRange.extendWithRanges(changedRanges, blockDiff);
2944 if (composition && !changedRanges.some(r => r.fromA <= composition.range.fromA && r.toA >= composition.range.toA))
2945 changedRanges = composition.range.addToSet(changedRanges.slice());
2946 if ((this.tile.flags & 2 /* TileFlag.Synced */) && changedRanges.length == 0) {
2947 return false;
2948 }
2949 else {
2950 this.updateInner(changedRanges, composition);
2951 if (update.transactions.length)
2952 this.lastUpdate = Date.now();
2953 return true;
2954 }
2955 }
2956 // Used by update and the constructor do perform the actual DOM
2957 // update
2958 updateInner(changes, composition) {
2959 this.view.viewState.mustMeasureContent = true;
2960 let { observer } = this.view;
2961 observer.ignore(() => {
2962 if (composition || changes.length) {
2963 let oldTile = this.tile;
2964 let builder = new TileUpdate(this.view, oldTile, this.blockWrappers, this.decorations, this.dynamicDecorationMap);
2965 if (composition && Tile.get(composition.text))
2966 builder.cache.reused.set(Tile.get(composition.text), 2 /* Reused.DOM */);
2967 this.tile = builder.run(changes, composition);
2968 destroyDropped(oldTile, builder.cache.reused);
2969 }
2970 // Lock the height during redrawing, since Chrome sometimes
2971 // messes with the scroll position during DOM mutation (though
2972 // no relayout is triggered and I cannot imagine how it can
2973 // recompute the scroll position without a layout)
2974 this.tile.dom.style.height = this.view.viewState.contentHeight / this.view.scaleY + "px";
2975 this.tile.dom.style.flexBasis = this.minWidth ? this.minWidth + "px" : "";
2976 // Chrome will sometimes, when DOM mutations occur directly
2977 // around the selection, get confused and report a different
2978 // selection from the one it displays (issue #218). This tries
2979 // to detect that situation.
2980 let track = browser.chrome || browser.ios ? { node: observer.selectionRange.focusNode, written: false } : undefined;
2981 this.tile.sync(track);
2982 if (track && (track.written || observer.selectionRange.focusNode != track.node || !this.tile.dom.contains(track.node)))
2983 this.forceSelection = true;
2984 this.tile.dom.style.height = "";
2985 });
2986 let gaps = [];
2987 if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
2988 for (let child of this.tile.children)
2989 if (child.isWidget() && child.widget instanceof BlockGapWidget)
2990 gaps.push(child.dom);
2991 observer.updateGaps(gaps);
2992 }
2993 updateEditContextFormatting(update) {
2994 this.editContextFormatting = this.editContextFormatting.map(update.changes);
2995 for (let tr of update.transactions)
2996 for (let effect of tr.effects)
2997 if (effect.is(setEditContextFormatting)) {
2998 this.editContextFormatting = effect.value;
2999 }
3000 }
3001 // Sync the DOM selection to this.state.selection
3002 updateSelection(mustRead = false, fromPointer = false) {
3003 if (mustRead || !this.view.observer.selectionRange.focusNode)
3004 this.view.observer.readSelectionRange();
3005 let { dom } = this.tile;
3006 let activeElt = this.view.root.activeElement, focused = activeElt == dom;
3007 let selectionNotFocus = !focused && !(this.view.state.facet(editable) || dom.tabIndex > -1) &&
3008 hasSelection(dom, this.view.observer.selectionRange) && !(activeElt && dom.contains(activeElt));
3009 if (!(focused || fromPointer || selectionNotFocus))
3010 return;
3011 let force = this.forceSelection;
3012 this.forceSelection = false;
3013 let main = this.view.state.selection.main, anchor, head;
3014 if (main.empty) {
3015 head = anchor = this.inlineDOMNearPos(main.anchor, main.assoc || 1);
3016 }
3017 else {
3018 head = this.inlineDOMNearPos(main.head, main.head == main.from ? 1 : -1);
3019 anchor = this.inlineDOMNearPos(main.anchor, main.anchor == main.from ? 1 : -1);
3020 }
3021 // Always reset on Firefox when next to an uneditable node to
3022 // avoid invisible cursor bugs (#111)
3023 if (browser.gecko && main.empty && !this.hasComposition && betweenUneditable(anchor)) {
3024 let dummy = document.createTextNode("");
3025 this.view.observer.ignore(() => anchor.node.insertBefore(dummy, anchor.node.childNodes[anchor.offset] || null));
3026 anchor = head = new DOMPos(dummy, 0);
3027 force = true;
3028 }
3029 let domSel = this.view.observer.selectionRange;
3030 // If the selection is already here, or in an equivalent position, don't touch it
3031 if (force || !domSel.focusNode || (!isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
3032 !isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) && !this.suppressWidgetCursorChange(domSel, main)) {
3033 this.view.observer.ignore(() => {
3034 // Chrome Android will hide the virtual keyboard when tapping
3035 // inside an uneditable node, and not bring it back when we
3036 // move the cursor to its proper position. This tries to
3037 // restore the keyboard by cycling focus.
3038 if (browser.android && browser.chrome && dom.contains(domSel.focusNode) &&
3039 inUneditable(domSel.focusNode, dom)) {
3040 dom.blur();
3041 dom.focus({ preventScroll: true });
3042 }
3043 let rawSel = getSelection(this.view.root);
3044 if (!rawSel) ;
3045 else if (main.empty) {
3046 // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
3047 if (browser.gecko) {
3048 let nextTo = nextToUneditable(anchor.node, anchor.offset);
3049 if (nextTo && nextTo != (1 /* NextTo.Before */ | 2 /* NextTo.After */)) {
3050 let text = (nextTo == 1 /* NextTo.Before */ ? textNodeBefore : textNodeAfter)(anchor.node, anchor.offset);
3051 if (text)
3052 anchor = new DOMPos(text.node, text.offset);
3053 }
3054 }
3055 rawSel.collapse(anchor.node, anchor.offset);
3056 if (main.bidiLevel != null && rawSel.caretBidiLevel !== undefined)
3057 rawSel.caretBidiLevel = main.bidiLevel;
3058 }
3059 else if (rawSel.extend) {
3060 // Selection.extend can be used to create an 'inverted' selection
3061 // (one where the focus is before the anchor), but not all
3062 // browsers support it yet.
3063 rawSel.collapse(anchor.node, anchor.offset);
3064 // Safari will ignore the call above when the editor is
3065 // hidden, and then raise an error on the call to extend
3066 // (#940).
3067 try {
3068 rawSel.extend(head.node, head.offset);
3069 }
3070 catch (_) { }
3071 }
3072 else {
3073 // Primitive (IE) way
3074 let range = document.createRange();
3075 if (main.anchor > main.head)
3076 [anchor, head] = [head, anchor];
3077 range.setEnd(head.node, head.offset);
3078 range.setStart(anchor.node, anchor.offset);
3079 rawSel.removeAllRanges();
3080 rawSel.addRange(range);
3081 }
3082 if (selectionNotFocus && this.view.root.activeElement == dom) {
3083 dom.blur();
3084 if (activeElt)
3085 activeElt.focus();
3086 }
3087 });
3088 this.view.observer.setSelectionRange(anchor, head);
3089 }
3090 this.impreciseAnchor = anchor.precise ? null : new DOMPos(domSel.anchorNode, domSel.anchorOffset);
3091 this.impreciseHead = head.precise ? null : new DOMPos(domSel.focusNode, domSel.focusOffset);
3092 }
3093 // If a zero-length widget is inserted next to the cursor during
3094 // composition, avoid moving it across it and disrupting the
3095 // composition.
3096 suppressWidgetCursorChange(sel, cursor) {
3097 return this.hasComposition && cursor.empty &&
3098 isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset) &&
3099 this.posFromDOM(sel.focusNode, sel.focusOffset) == cursor.head;
3100 }
3101 enforceCursorAssoc() {
3102 if (this.hasComposition)
3103 return;
3104 let { view } = this, cursor = view.state.selection.main;
3105 let sel = getSelection(view.root);
3106 let { anchorNode, anchorOffset } = view.observer.selectionRange;
3107 if (!sel || !cursor.empty || !cursor.assoc || !sel.modify)
3108 return;
3109 let line = this.lineAt(cursor.head, cursor.assoc);
3110 if (!line)
3111 return;
3112 let lineStart = line.posAtStart;
3113 if (cursor.head == lineStart || cursor.head == lineStart + line.length)
3114 return;
3115 let before = this.coordsAt(cursor.head, -1), after = this.coordsAt(cursor.head, 1);
3116 if (!before || !after || before.bottom > after.top)
3117 return;
3118 let dom = this.domAtPos(cursor.head + cursor.assoc, cursor.assoc);
3119 sel.collapse(dom.node, dom.offset);
3120 sel.modify("move", cursor.assoc < 0 ? "forward" : "backward", "lineboundary");
3121 // This can go wrong in corner cases like single-character lines,
3122 // so check and reset if necessary.
3123 view.observer.readSelectionRange();
3124 let newRange = view.observer.selectionRange;
3125 if (view.docView.posFromDOM(newRange.anchorNode, newRange.anchorOffset) != cursor.from)
3126 sel.collapse(anchorNode, anchorOffset);
3127 }
3128 posFromDOM(node, offset) {
3129 let tile = this.tile.nearest(node);
3130 if (!tile)
3131 return this.tile.dom.compareDocumentPosition(node) & 2 /* PRECEDING */ ? 0 : this.view.state.doc.length;
3132 let start = tile.posAtStart;
3133 if (tile.isComposite()) {
3134 let after;
3135 if (node == tile.dom) {
3136 after = tile.dom.childNodes[offset];
3137 }
3138 else {
3139 let bias = maxOffset(node) == 0 ? 0 : offset == 0 ? -1 : 1;
3140 for (;;) {
3141 let parent = node.parentNode;
3142 if (parent == tile.dom)
3143 break;
3144 if (bias == 0 && parent.firstChild != parent.lastChild) {
3145 if (node == parent.firstChild)
3146 bias = -1;
3147 else
3148 bias = 1;
3149 }
3150 node = parent;
3151 }
3152 if (bias < 0)
3153 after = node;
3154 else
3155 after = node.nextSibling;
3156 }
3157 if (after == tile.dom.firstChild)
3158 return start;
3159 while (after && !Tile.get(after))
3160 after = after.nextSibling;
3161 if (!after)
3162 return start + tile.length;
3163 for (let i = 0, pos = start;; i++) {
3164 let child = tile.children[i];
3165 if (child.dom == after)
3166 return pos;
3167 pos += child.length + child.breakAfter;
3168 }
3169 }
3170 else if (tile.isText()) {
3171 return node == tile.dom ? start + offset : start + (offset ? tile.length : 0);
3172 }
3173 else {
3174 return start;
3175 }
3176 }
3177 domAtPos(pos, side) {
3178 let { tile, offset } = this.tile.resolveBlock(pos, side);
3179 if (tile.isWidget())
3180 return tile.domPosFor(pos, side);
3181 return tile.domIn(offset, side);
3182 }
3183 inlineDOMNearPos(pos, side) {
3184 let before, beforeOff = -1, beforeBad = false;
3185 let after, afterOff = -1, afterBad = false;
3186 this.tile.blockTiles((tile, off) => {
3187 if (tile.isWidget()) {
3188 if ((tile.flags & 32 /* TileFlag.After */) && off >= pos)
3189 return true;
3190 if (tile.flags & 16 /* TileFlag.Before */)
3191 beforeBad = true;
3192 }
3193 else {
3194 let end = off + tile.length;
3195 if (off <= pos) {
3196 before = tile;
3197 beforeOff = pos - off;
3198 beforeBad = end < pos;
3199 }
3200 if (end >= pos && !after) {
3201 after = tile;
3202 afterOff = pos - off;
3203 afterBad = off > pos;
3204 }
3205 if (off > pos && after)
3206 return true;
3207 }
3208 });
3209 if (!before && !after)
3210 return this.domAtPos(pos, side);
3211 if (beforeBad && after)
3212 before = null;
3213 else if (afterBad && before)
3214 after = null;
3215 return before && side < 0 || !after ? before.domIn(beforeOff, side) : after.domIn(afterOff, side);
3216 }
3217 coordsAt(pos, side) {
3218 let { tile, offset } = this.tile.resolveBlock(pos, side);
3219 if (tile.isWidget()) {
3220 if (tile.widget instanceof BlockGapWidget)
3221 return null;
3222 return tile.coordsInWidget(offset, side, true);
3223 }
3224 return tile.coordsIn(offset, side);
3225 }
3226 lineAt(pos, side) {
3227 let { tile } = this.tile.resolveBlock(pos, side);
3228 return tile.isLine() ? tile : null;
3229 }
3230 coordsForChar(pos) {
3231 let { tile, offset } = this.tile.resolveBlock(pos, 1);
3232 if (!tile.isLine())
3233 return null;
3234 function scan(tile, offset) {
3235 if (tile.isComposite()) {
3236 for (let ch of tile.children) {
3237 if (ch.length >= offset) {
3238 let found = scan(ch, offset);
3239 if (found)
3240 return found;
3241 }
3242 offset -= ch.length;
3243 if (offset < 0)
3244 break;
3245 }
3246 }
3247 else if (tile.isText() && offset < tile.length) {
3248 let end = state.findClusterBreak(tile.text, offset);
3249 if (end == offset)
3250 return null;
3251 let rects = textRange(tile.dom, offset, end).getClientRects();
3252 for (let i = 0; i < rects.length; i++) {
3253 let rect = rects[i];
3254 if (i == rects.length - 1 || rect.top < rect.bottom && rect.left < rect.right)
3255 return rect;
3256 }
3257 }
3258 return null;
3259 }
3260 return scan(tile, offset);
3261 }
3262 measureVisibleLineHeights(viewport) {
3263 let result = [], { from, to } = viewport;
3264 let contentWidth = this.view.contentDOM.clientWidth;
3265 let isWider = contentWidth > Math.max(this.view.scrollDOM.clientWidth, this.minWidth) + 1;
3266 let widest = -1, ltr = this.view.textDirection == exports.Direction.LTR;
3267 let spaceAbove = 0;
3268 let scan = (tile, pos, measureBounds) => {
3269 for (let i = 0; i < tile.children.length; i++) {
3270 if (pos > to)
3271 break;
3272 let child = tile.children[i], end = pos + child.length;
3273 let childRect = child.dom.getBoundingClientRect(), { height } = childRect;
3274 if (measureBounds && !i)
3275 spaceAbove += childRect.top - measureBounds.top;
3276 if (child instanceof BlockWrapperTile) {
3277 if (end > from)
3278 scan(child, pos, childRect);
3279 }
3280 else if (pos >= from) {
3281 if (spaceAbove > 0)
3282 result.push(-spaceAbove);
3283 result.push(height + spaceAbove);
3284 spaceAbove = 0;
3285 if (isWider) {
3286 let last = child.dom.lastChild;
3287 let rects = last ? clientRectsFor(last) : [];
3288 if (rects.length) {
3289 let rect = rects[rects.length - 1];
3290 let width = ltr ? rect.right - childRect.left : childRect.right - rect.left;
3291 if (width > widest) {
3292 widest = width;
3293 this.minWidth = contentWidth;
3294 this.minWidthFrom = pos;
3295 this.minWidthTo = end;
3296 }
3297 }
3298 }
3299 }
3300 if (measureBounds && i == tile.children.length - 1)
3301 spaceAbove += measureBounds.bottom - childRect.bottom;
3302 pos = end + child.breakAfter;
3303 }
3304 };
3305 scan(this.tile, 0, null);
3306 return result;
3307 }
3308 textDirectionAt(pos) {
3309 let { tile } = this.tile.resolveBlock(pos, 1);
3310 return getComputedStyle(tile.dom).direction == "rtl" ? exports.Direction.RTL : exports.Direction.LTR;
3311 }
3312 measureTextSize() {
3313 let lineMeasure = this.tile.blockTiles(tile => {
3314 if (tile.isLine() && tile.children.length && tile.length <= 20) {
3315 let totalWidth = 0, textHeight;
3316 for (let child of tile.children) {
3317 if (!child.isText() || /[^ -~]/.test(child.text))
3318 return undefined;
3319 let rects = clientRectsFor(child.dom);
3320 if (rects.length != 1)
3321 return undefined;
3322 totalWidth += rects[0].width;
3323 textHeight = rects[0].height;
3324 }
3325 if (totalWidth)
3326 return {
3327 lineHeight: tile.dom.getBoundingClientRect().height,
3328 charWidth: totalWidth / tile.length,
3329 textHeight
3330 };
3331 }
3332 });
3333 if (lineMeasure)
3334 return lineMeasure;
3335 // If no workable line exists, force a layout of a measurable element
3336 let dummy = document.createElement("div"), lineHeight, charWidth, textHeight;
3337 dummy.className = "cm-line";
3338 dummy.style.width = "99999px";
3339 dummy.style.position = "absolute";
3340 dummy.textContent = "abc def ghi jkl mno pqr stu";
3341 this.view.observer.ignore(() => {
3342 this.tile.dom.appendChild(dummy);
3343 let rect = clientRectsFor(dummy.firstChild)[0];
3344 lineHeight = dummy.getBoundingClientRect().height;
3345 charWidth = rect && rect.width ? rect.width / 27 : 7;
3346 textHeight = rect && rect.height ? rect.height : lineHeight;
3347 dummy.remove();
3348 });
3349 return { lineHeight, charWidth, textHeight };
3350 }
3351 computeBlockGapDeco() {
3352 let deco = [], vs = this.view.viewState;
3353 for (let pos = 0, i = 0;; i++) {
3354 let next = i == vs.viewports.length ? null : vs.viewports[i];
3355 let end = next ? next.from - 1 : this.view.state.doc.length;
3356 if (end > pos) {
3357 let height = (vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top) / this.view.scaleY;
3358 deco.push(Decoration.replace({
3359 widget: new BlockGapWidget(height),
3360 block: true,
3361 inclusive: true,
3362 isBlockGap: true,
3363 }).range(pos, end));
3364 }
3365 if (!next)
3366 break;
3367 pos = next.to + 1;
3368 }
3369 return Decoration.set(deco);
3370 }
3371 updateDeco() {
3372 let i = 1;
3373 let allDeco = this.view.state.facet(decorations).map(d => {
3374 let dynamic = this.dynamicDecorationMap[i++] = typeof d == "function";
3375 return dynamic ? d(this.view) : d;
3376 });
3377 let dynamicOuter = false, outerDeco = this.view.state.facet(outerDecorations).map((d, i) => {
3378 let dynamic = typeof d == "function";
3379 if (dynamic)
3380 dynamicOuter = true;
3381 return dynamic ? d(this.view) : d;
3382 });
3383 if (outerDeco.length) {
3384 this.dynamicDecorationMap[i++] = dynamicOuter;
3385 allDeco.push(state.RangeSet.join(outerDeco));
3386 }
3387 this.decorations = [
3388 this.editContextFormatting,
3389 ...allDeco,
3390 this.computeBlockGapDeco(),
3391 this.view.viewState.lineGapDeco
3392 ];
3393 while (i < this.decorations.length)
3394 this.dynamicDecorationMap[i++] = false;
3395 this.blockWrappers = this.view.state.facet(blockWrappers).map(v => typeof v == "function" ? v(this.view) : v);
3396 }
3397 scrollIntoView(target) {
3398 if (target.isSnapshot) {
3399 let ref = this.view.viewState.lineBlockAt(target.range.head);
3400 this.view.scrollDOM.scrollTop = ref.top - target.yMargin;
3401 this.view.scrollDOM.scrollLeft = target.xMargin;
3402 return;
3403 }
3404 for (let handler of this.view.state.facet(scrollHandler)) {
3405 try {
3406 if (handler(this.view, target.range, target))
3407 return true;
3408 }
3409 catch (e) {
3410 logException(this.view.state, e, "scroll handler");
3411 }
3412 }
3413 let { range } = target;
3414 let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
3415 if (!rect)
3416 return;
3417 if (!range.empty && (other = this.coordsAt(range.anchor, range.anchor > range.head ? -1 : 1)))
3418 rect = { left: Math.min(rect.left, other.left), top: Math.min(rect.top, other.top),
3419 right: Math.max(rect.right, other.right), bottom: Math.max(rect.bottom, other.bottom) };
3420 let margins = getScrollMargins(this.view);
3421 let targetRect = {
3422 left: rect.left - margins.left, top: rect.top - margins.top,
3423 right: rect.right + margins.right, bottom: rect.bottom + margins.bottom
3424 };
3425 let { offsetWidth, offsetHeight } = this.view.scrollDOM;
3426 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 == exports.Direction.LTR);
3427 // On mobile browsers, the visual viewport may be smaller than the
3428 // actual reported viewport, causing scrollRectIntoView to fail to
3429 // scroll properly. Unfortunately, this visual viewport cannot be
3430 // updated directly, and scrollIntoView is the only way a script
3431 // can affect it. So this tries to kludge around the problem by
3432 // calling scrollIntoView on the scroll target's line.
3433 if (window.visualViewport && window.innerHeight - window.visualViewport.height > 1 &&
3434 (rect.top > window.pageYOffset + window.visualViewport.offsetTop + window.visualViewport.height ||
3435 rect.bottom < window.pageYOffset + window.visualViewport.offsetTop)) {
3436 let line = this.view.docView.lineAt(range.head, 1);
3437 if (line)
3438 line.dom.scrollIntoView({ block: "nearest" });
3439 }
3440 }
3441 lineHasWidget(pos) {
3442 let scan = (child) => child.isWidget() || child.children.some(scan);
3443 return scan(this.tile.resolveBlock(pos, 1).tile);
3444 }
3445 destroy() {
3446 destroyDropped(this.tile);
3447 }
3448}
3449function destroyDropped(tile, reused) {
3450 let r = reused === null || reused === void 0 ? void 0 : reused.get(tile);
3451 if (r != 1 /* Reused.Full */) {
3452 if (r == null)
3453 tile.destroy();
3454 for (let ch of tile.children)
3455 destroyDropped(ch, reused);
3456 }
3457}
3458function betweenUneditable(pos) {
3459 return pos.node.nodeType == 1 && pos.node.firstChild &&
3460 (pos.offset == 0 || pos.node.childNodes[pos.offset - 1].contentEditable == "false") &&
3461 (pos.offset == pos.node.childNodes.length || pos.node.childNodes[pos.offset].contentEditable == "false");
3462}
3463function findCompositionNode(view, headPos) {
3464 let sel = view.observer.selectionRange;
3465 if (!sel.focusNode)
3466 return null;
3467 let textBefore = textNodeBefore(sel.focusNode, sel.focusOffset);
3468 let textAfter = textNodeAfter(sel.focusNode, sel.focusOffset);
3469 let textNode = textBefore || textAfter;
3470 if (textAfter && textBefore && textAfter.node != textBefore.node) {
3471 let tileAfter = Tile.get(textAfter.node);
3472 if (!tileAfter || tileAfter.isText() && tileAfter.text != textAfter.node.nodeValue) {
3473 textNode = textAfter;
3474 }
3475 else if (view.docView.lastCompositionAfterCursor) {
3476 let tileBefore = Tile.get(textBefore.node);
3477 if (!(!tileBefore || tileBefore.isText() && tileBefore.text != textBefore.node.nodeValue))
3478 textNode = textAfter;
3479 }
3480 }
3481 view.docView.lastCompositionAfterCursor = textNode != textBefore;
3482 if (!textNode)
3483 return null;
3484 let from = headPos - textNode.offset;
3485 return { from, to: from + textNode.node.nodeValue.length, node: textNode.node };
3486}
3487function findCompositionRange(view, changes, headPos) {
3488 let found = findCompositionNode(view, headPos);
3489 if (!found)
3490 return null;
3491 let { node: textNode, from, to } = found, text = textNode.nodeValue;
3492 // Don't try to preserve multi-line compositions
3493 if (/[\n\r]/.test(text))
3494 return null;
3495 if (view.state.doc.sliceString(found.from, found.to) != text)
3496 return null;
3497 let inv = changes.invertedDesc;
3498 return { range: new ChangedRange(inv.mapPos(from), inv.mapPos(to), from, to), text: textNode };
3499}
3500function nextToUneditable(node, offset) {
3501 if (node.nodeType != 1)
3502 return 0;
3503 return (offset && node.childNodes[offset - 1].contentEditable == "false" ? 1 /* NextTo.Before */ : 0) |
3504 (offset < node.childNodes.length && node.childNodes[offset].contentEditable == "false" ? 2 /* NextTo.After */ : 0);
3505}
3506let DecorationComparator$1 = class DecorationComparator {
3507 constructor() {
3508 this.changes = [];
3509 }
3510 compareRange(from, to) { addRange(from, to, this.changes); }
3511 comparePoint(from, to) { addRange(from, to, this.changes); }
3512 boundChange(pos) { addRange(pos, pos, this.changes); }
3513};
3514function findChangedDeco(a, b, diff) {
3515 let comp = new DecorationComparator$1;
3516 state.RangeSet.compare(a, b, diff, comp);
3517 return comp.changes;
3518}
3519class WrapperComparator {
3520 constructor() {
3521 this.changes = [];
3522 }
3523 compareRange(from, to) { addRange(from, to, this.changes); }
3524 comparePoint() { }
3525 boundChange(pos) { addRange(pos, pos, this.changes); }
3526}
3527function findChangedWrappers(a, b, diff) {
3528 let comp = new WrapperComparator;
3529 state.RangeSet.compare(a, b, diff, comp);
3530 return comp.changes;
3531}
3532function inUneditable(node, inside) {
3533 for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
3534 if (cur.nodeType == 1 && cur.contentEditable == 'false') {
3535 return true;
3536 }
3537 }
3538 return false;
3539}
3540function touchesComposition(changes, composition) {
3541 let touched = false;
3542 if (composition)
3543 changes.iterChangedRanges((from, to) => {
3544 if (from < composition.to && to > composition.from)
3545 touched = true;
3546 });
3547 return touched;
3548}
3549class BlockGapWidget extends WidgetType {
3550 constructor(height) {
3551 super();
3552 this.height = height;
3553 }
3554 toDOM() {
3555 let elt = document.createElement("div");
3556 elt.className = "cm-gap";
3557 this.updateDOM(elt);
3558 return elt;
3559 }
3560 eq(other) { return other.height == this.height; }
3561 updateDOM(elt) {
3562 elt.style.height = this.height + "px";
3563 return true;
3564 }
3565 get editable() { return true; }
3566 get estimatedHeight() { return this.height; }
3567 ignoreEvent() { return false; }
3568}
3569
3570function groupAt(state$1, pos, bias = 1) {
3571 let categorize = state$1.charCategorizer(pos);
3572 let line = state$1.doc.lineAt(pos), linePos = pos - line.from;
3573 if (line.length == 0)
3574 return state.EditorSelection.cursor(pos);
3575 if (linePos == 0)
3576 bias = 1;
3577 else if (linePos == line.length)
3578 bias = -1;
3579 let from = linePos, to = linePos;
3580 if (bias < 0)
3581 from = state.findClusterBreak(line.text, linePos, false);
3582 else
3583 to = state.findClusterBreak(line.text, linePos);
3584 let cat = categorize(line.text.slice(from, to));
3585 while (from > 0) {
3586 let prev = state.findClusterBreak(line.text, from, false);
3587 if (categorize(line.text.slice(prev, from)) != cat)
3588 break;
3589 from = prev;
3590 }
3591 while (to < line.length) {
3592 let next = state.findClusterBreak(line.text, to);
3593 if (categorize(line.text.slice(to, next)) != cat)
3594 break;
3595 to = next;
3596 }
3597 return state.EditorSelection.range(from + line.from, to + line.from);
3598}
3599function posAtCoordsImprecise(view, contentRect, block, x, y) {
3600 let into = Math.round((x - contentRect.left) * view.defaultCharacterWidth);
3601 if (view.lineWrapping && block.height > view.defaultLineHeight * 1.5) {
3602 let textHeight = view.viewState.heightOracle.textHeight;
3603 let line = Math.floor((y - block.top - (view.defaultLineHeight - textHeight) * 0.5) / textHeight);
3604 into += line * view.viewState.heightOracle.lineLength;
3605 }
3606 let content = view.state.sliceDoc(block.from, block.to);
3607 return block.from + state.findColumn(content, into, view.state.tabSize);
3608}
3609function blockAt(view, pos, side) {
3610 let line = view.lineBlockAt(pos);
3611 if (Array.isArray(line.type)) {
3612 let best;
3613 for (let l of line.type) {
3614 if (l.from > pos)
3615 break;
3616 if (l.to < pos)
3617 continue;
3618 if (l.from < pos && l.to > pos)
3619 return l;
3620 if (!best || (l.type == exports.BlockType.Text && (best.type != l.type || (side < 0 ? l.from < pos : l.to > pos))))
3621 best = l;
3622 }
3623 return best || line;
3624 }
3625 return line;
3626}
3627function moveToLineBoundary(view, start, forward, includeWrap) {
3628 let line = blockAt(view, start.head, start.assoc || -1);
3629 let coords = !includeWrap || line.type != exports.BlockType.Text || !(view.lineWrapping || line.widgetLineBreaks) ? null
3630 : view.coordsAtPos(start.assoc < 0 && start.head > line.from ? start.head - 1 : start.head);
3631 if (coords) {
3632 let editorRect = view.dom.getBoundingClientRect();
3633 let direction = view.textDirectionAt(line.from);
3634 let pos = view.posAtCoords({ x: forward == (direction == exports.Direction.LTR) ? editorRect.right - 1 : editorRect.left + 1,
3635 y: (coords.top + coords.bottom) / 2 });
3636 if (pos != null)
3637 return state.EditorSelection.cursor(pos, forward ? -1 : 1);
3638 }
3639 return state.EditorSelection.cursor(forward ? line.to : line.from, forward ? -1 : 1);
3640}
3641function moveByChar(view, start, forward, by) {
3642 let line = view.state.doc.lineAt(start.head), spans = view.bidiSpans(line);
3643 let direction = view.textDirectionAt(line.from);
3644 for (let cur = start, check = null;;) {
3645 let next = moveVisually(line, spans, direction, cur, forward), char = movedOver;
3646 if (!next) {
3647 if (line.number == (forward ? view.state.doc.lines : 1))
3648 return cur;
3649 char = "\n";
3650 line = view.state.doc.line(line.number + (forward ? 1 : -1));
3651 spans = view.bidiSpans(line);
3652 next = view.visualLineSide(line, !forward);
3653 }
3654 if (!check) {
3655 if (!by)
3656 return next;
3657 check = by(char);
3658 }
3659 else if (!check(char)) {
3660 return cur;
3661 }
3662 cur = next;
3663 }
3664}
3665function byGroup(view, pos, start) {
3666 let categorize = view.state.charCategorizer(pos);
3667 let cat = categorize(start);
3668 return (next) => {
3669 let nextCat = categorize(next);
3670 if (cat == state.CharCategory.Space)
3671 cat = nextCat;
3672 return cat == nextCat;
3673 };
3674}
3675function moveVertically(view, start, forward, distance) {
3676 let startPos = start.head, dir = forward ? 1 : -1;
3677 if (startPos == (forward ? view.state.doc.length : 0))
3678 return state.EditorSelection.cursor(startPos, start.assoc);
3679 let goal = start.goalColumn, startY;
3680 let rect = view.contentDOM.getBoundingClientRect();
3681 let startCoords = view.coordsAtPos(startPos, (start.empty ? start.assoc : 0) || (forward ? 1 : -1)), docTop = view.documentTop;
3682 if (startCoords) {
3683 if (goal == null)
3684 goal = startCoords.left - rect.left;
3685 startY = dir < 0 ? startCoords.top : startCoords.bottom;
3686 }
3687 else {
3688 let line = view.viewState.lineBlockAt(startPos);
3689 if (goal == null)
3690 goal = Math.min(rect.right - rect.left, view.defaultCharacterWidth * (startPos - line.from));
3691 startY = (dir < 0 ? line.top : line.bottom) + docTop;
3692 }
3693 let resolvedGoal = rect.left + goal;
3694 let dist = distance !== null && distance !== void 0 ? distance : (view.viewState.heightOracle.textHeight >> 1);
3695 let pos = posAtCoords(view, { x: resolvedGoal, y: startY + dist * dir }, false, dir);
3696 return state.EditorSelection.cursor(pos.pos, pos.assoc, undefined, goal);
3697}
3698function skipAtomicRanges(atoms, pos, bias) {
3699 for (;;) {
3700 let moved = 0;
3701 for (let set of atoms) {
3702 set.between(pos - 1, pos + 1, (from, to, value) => {
3703 if (pos > from && pos < to) {
3704 let side = moved || bias || (pos - from < to - pos ? -1 : 1);
3705 pos = side < 0 ? from : to;
3706 moved = side;
3707 }
3708 });
3709 }
3710 if (!moved)
3711 return pos;
3712 }
3713}
3714function skipAtomsForSelection(atoms, sel) {
3715 let ranges = null;
3716 for (let i = 0; i < sel.ranges.length; i++) {
3717 let range = sel.ranges[i], updated = null;
3718 if (range.empty) {
3719 let pos = skipAtomicRanges(atoms, range.from, 0);
3720 if (pos != range.from)
3721 updated = state.EditorSelection.cursor(pos, -1);
3722 }
3723 else {
3724 let from = skipAtomicRanges(atoms, range.from, -1);
3725 let to = skipAtomicRanges(atoms, range.to, 1);
3726 if (from != range.from || to != range.to)
3727 updated = state.EditorSelection.range(range.from == range.anchor ? from : to, range.from == range.head ? from : to);
3728 }
3729 if (updated) {
3730 if (!ranges)
3731 ranges = sel.ranges.slice();
3732 ranges[i] = updated;
3733 }
3734 }
3735 return ranges ? state.EditorSelection.create(ranges, sel.mainIndex) : sel;
3736}
3737function skipAtoms(view, oldPos, pos) {
3738 let newPos = skipAtomicRanges(view.state.facet(atomicRanges).map(f => f(view)), pos.from, oldPos.head > pos.from ? -1 : 1);
3739 return newPos == pos.from ? pos : state.EditorSelection.cursor(newPos, newPos < pos.from ? 1 : -1);
3740}
3741class PosAssoc {
3742 constructor(pos, assoc) {
3743 this.pos = pos;
3744 this.assoc = assoc;
3745 }
3746}
3747function posAtCoords(view, coords, precise, scanY) {
3748 let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
3749 let { x, y } = coords, yOffset = y - docTop, block;
3750 // First find the block at the given Y position, if any. If scanY is
3751 // given (used for vertical cursor motion), try to skip widgets and
3752 // line padding.
3753 for (;;) {
3754 if (yOffset < 0)
3755 return new PosAssoc(0, 1);
3756 if (yOffset > view.viewState.docHeight)
3757 return new PosAssoc(view.state.doc.length, -1);
3758 block = view.elementAtHeight(yOffset);
3759 if (scanY == null)
3760 break;
3761 if (block.type == exports.BlockType.Text) {
3762 if (scanY < 0 ? block.to < view.viewport.from : block.from > view.viewport.to)
3763 break;
3764 // Check whether we aren't landing on the top/bottom padding of the line
3765 let rect = view.docView.coordsAt(scanY < 0 ? block.from : block.to, scanY > 0 ? -1 : 1);
3766 if (rect && (scanY < 0 ? rect.top <= yOffset + docTop : rect.bottom >= yOffset + docTop))
3767 break;
3768 }
3769 let halfLine = view.viewState.heightOracle.textHeight / 2;
3770 yOffset = scanY > 0 ? block.bottom + halfLine : block.top - halfLine;
3771 }
3772 // If outside the viewport, return null if precise==true, an
3773 // estimate otherwise.
3774 if (view.viewport.from >= block.to || view.viewport.to <= block.from) {
3775 if (precise)
3776 return null;
3777 if (block.type == exports.BlockType.Text) {
3778 let pos = posAtCoordsImprecise(view, content, block, x, y);
3779 return new PosAssoc(pos, pos == block.from ? 1 : -1);
3780 }
3781 }
3782 if (block.type != exports.BlockType.Text)
3783 return yOffset < (block.top + block.bottom) / 2 ? new PosAssoc(block.from, 1) : new PosAssoc(block.to, -1);
3784 // Here we know we're in a line, so run the logic for inline layout
3785 let line = view.docView.lineAt(block.from, 2);
3786 if (!line || line.length != block.length)
3787 line = view.docView.lineAt(block.from, -2);
3788 return new InlineCoordsScan(view, x, y, view.textDirectionAt(block.from)).scanTile(line, block.from);
3789}
3790class InlineCoordsScan {
3791 constructor(view, x, y, baseDir) {
3792 this.view = view;
3793 this.x = x;
3794 this.y = y;
3795 this.baseDir = baseDir;
3796 // Cached bidi info
3797 this.line = null;
3798 this.spans = null;
3799 }
3800 bidiSpansAt(pos) {
3801 if (!this.line || this.line.from > pos || this.line.to < pos) {
3802 this.line = this.view.state.doc.lineAt(pos);
3803 this.spans = this.view.bidiSpans(this.line);
3804 }
3805 return this;
3806 }
3807 baseDirAt(pos, side) {
3808 let { line, spans } = this.bidiSpansAt(pos);
3809 let level = spans[BidiSpan.find(spans, pos - line.from, -1, side)].level;
3810 return level == this.baseDir;
3811 }
3812 dirAt(pos, side) {
3813 let { line, spans } = this.bidiSpansAt(pos);
3814 return spans[BidiSpan.find(spans, pos - line.from, -1, side)].dir;
3815 }
3816 // Used to short-circuit bidi tests for content with a uniform direction
3817 bidiIn(from, to) {
3818 let { spans, line } = this.bidiSpansAt(from);
3819 return spans.length > 1 || spans.length && (spans[0].level != this.baseDir || spans[0].to + line.from < to);
3820 }
3821 // Scan through the rectangles for the content of a tile with inline
3822 // content, looking for one that overlaps the queried position
3823 // vertically andis
3824 // closest horizontally. The caller is responsible for dividing its
3825 // content into N pieces, and pass an array with N+1 positions
3826 // (including the position after the last piece). For a text tile,
3827 // these will be character clusters, for a composite tile, these
3828 // will be child tiles.
3829 scan(positions, getRects) {
3830 let lo = 0, hi = positions.length - 1, seen = new Set();
3831 let bidi = this.bidiIn(positions[0], positions[hi]);
3832 let above, below;
3833 let closestI = -1, closestDx = 1e9, closestRect;
3834 // Because, when the content is bidirectional, a regular binary
3835 // search is hard to perform (the content order does not
3836 // correspond to visual order), this loop does something between a
3837 // regular binary search and a full scan, depending on what it can
3838 // get away with. The outer hi/lo bounds are only adjusted for
3839 // elements that are part of the base order.
3840 //
3841 // To make sure all elements inside those bounds are visited,
3842 // eventually, we keep a set of seen indices, and if the midpoint
3843 // has already been handled, we start in a random index within the
3844 // current bounds and scan forward until we find an index that
3845 // hasn't been seen yet.
3846 search: while (lo < hi) {
3847 let dist = hi - lo, mid = (lo + hi) >> 1;
3848 adjust: if (seen.has(mid)) {
3849 let scan = lo + Math.floor(Math.random() * dist);
3850 for (let i = 0; i < dist; i++) {
3851 if (!seen.has(scan)) {
3852 mid = scan;
3853 break adjust;
3854 }
3855 scan++;
3856 if (scan == hi)
3857 scan = lo; // Wrap around
3858 }
3859 break search; // No index found, we're done
3860 }
3861 seen.add(mid);
3862 let rects = getRects(mid);
3863 if (rects)
3864 for (let i = 0; i < rects.length; i++) {
3865 let rect = rects[i], side = 0;
3866 if (rect.bottom < this.y) {
3867 if (!above || above.bottom < rect.bottom)
3868 above = rect;
3869 side = 1;
3870 }
3871 else if (rect.top > this.y) {
3872 if (!below || below.top > rect.top)
3873 below = rect;
3874 side = -1;
3875 }
3876 else {
3877 let off = rect.left > this.x ? this.x - rect.left : rect.right < this.x ? this.x - rect.right : 0;
3878 let dx = Math.abs(off);
3879 if (dx < closestDx) {
3880 closestI = mid;
3881 closestDx = dx;
3882 closestRect = rect;
3883 }
3884 if (off)
3885 side = (off < 0) == (this.baseDir == exports.Direction.LTR) ? -1 : 1;
3886 }
3887 // Narrow binary search when it is safe to do so
3888 if (side == -1 && (!bidi || this.baseDirAt(positions[mid], 1)))
3889 hi = mid;
3890 else if (side == 1 && (!bidi || this.baseDirAt(positions[mid + 1], -1)))
3891 lo = mid + 1;
3892 }
3893 }
3894 // If no element with y overlap is found, find the nearest element
3895 // on the y axis, move this.y into it, and retry the scan.
3896 if (!closestRect) {
3897 let side = above && (!below || (this.y - above.bottom < below.top - this.y)) ? above : below;
3898 this.y = (side.top + side.bottom) / 2;
3899 return this.scan(positions, getRects);
3900 }
3901 let ltr = (bidi ? this.dirAt(positions[closestI], 1) : this.baseDir) == exports.Direction.LTR;
3902 return {
3903 i: closestI,
3904 // Test whether x is closes to the start or end of this element
3905 after: (this.x > (closestRect.left + closestRect.right) / 2) == ltr
3906 };
3907 }
3908 scanText(tile, offset) {
3909 let positions = [];
3910 for (let i = 0; i < tile.length; i = state.findClusterBreak(tile.text, i))
3911 positions.push(offset + i);
3912 positions.push(offset + tile.length);
3913 let scan = this.scan(positions, i => {
3914 let off = positions[i] - offset, end = positions[i + 1] - offset;
3915 return textRange(tile.dom, off, end).getClientRects();
3916 });
3917 return scan.after ? new PosAssoc(positions[scan.i + 1], -1) : new PosAssoc(positions[scan.i], 1);
3918 }
3919 scanTile(tile, offset) {
3920 if (!tile.length)
3921 return new PosAssoc(offset, 1);
3922 if (tile.children.length == 1) { // Short-circuit single-child tiles
3923 let child = tile.children[0];
3924 if (child.isText())
3925 return this.scanText(child, offset);
3926 else if (child.isComposite())
3927 return this.scanTile(child, offset);
3928 }
3929 let positions = [offset];
3930 for (let i = 0, pos = offset; i < tile.children.length; i++)
3931 positions.push(pos += tile.children[i].length);
3932 let scan = this.scan(positions, i => {
3933 let child = tile.children[i];
3934 if (child.flags & 48 /* TileFlag.PointWidget */)
3935 return null;
3936 return (child.dom.nodeType == 1 ? child.dom : textRange(child.dom, 0, child.length)).getClientRects();
3937 });
3938 let child = tile.children[scan.i], pos = positions[scan.i];
3939 if (child.isText())
3940 return this.scanText(child, pos);
3941 if (child.isComposite())
3942 return this.scanTile(child, pos);
3943 return scan.after ? new PosAssoc(positions[scan.i + 1], -1) : new PosAssoc(pos, 1);
3944 }
3945}
3946
3947const LineBreakPlaceholder = "\uffff";
3948class DOMReader {
3949 constructor(points, view) {
3950 this.points = points;
3951 this.view = view;
3952 this.text = "";
3953 this.lineSeparator = view.state.facet(state.EditorState.lineSeparator);
3954 }
3955 append(text) {
3956 this.text += text;
3957 }
3958 lineBreak() {
3959 this.text += LineBreakPlaceholder;
3960 }
3961 readRange(start, end) {
3962 if (!start)
3963 return this;
3964 let parent = start.parentNode;
3965 for (let cur = start;;) {
3966 this.findPointBefore(parent, cur);
3967 let oldLen = this.text.length;
3968 this.readNode(cur);
3969 let tile = Tile.get(cur), next = cur.nextSibling;
3970 if (next == end) {
3971 if ((tile === null || tile === void 0 ? void 0 : tile.breakAfter) && !next && parent != this.view.contentDOM)
3972 this.lineBreak();
3973 break;
3974 }
3975 let nextTile = Tile.get(next);
3976 if ((tile && nextTile ? tile.breakAfter :
3977 (tile ? tile.breakAfter : isBlockElement(cur)) ||
3978 (isBlockElement(next) && (cur.nodeName != "BR" || (tile === null || tile === void 0 ? void 0 : tile.isWidget())) && this.text.length > oldLen)) &&
3979 !isEmptyToEnd(next, end))
3980 this.lineBreak();
3981 cur = next;
3982 }
3983 this.findPointBefore(parent, end);
3984 return this;
3985 }
3986 readTextNode(node) {
3987 let text = node.nodeValue;
3988 for (let point of this.points)
3989 if (point.node == node)
3990 point.pos = this.text.length + Math.min(point.offset, text.length);
3991 for (let off = 0, re = this.lineSeparator ? null : /\r\n?|\n/g;;) {
3992 let nextBreak = -1, breakSize = 1, m;
3993 if (this.lineSeparator) {
3994 nextBreak = text.indexOf(this.lineSeparator, off);
3995 breakSize = this.lineSeparator.length;
3996 }
3997 else if (m = re.exec(text)) {
3998 nextBreak = m.index;
3999 breakSize = m[0].length;
4000 }
4001 this.append(text.slice(off, nextBreak < 0 ? text.length : nextBreak));
4002 if (nextBreak < 0)
4003 break;
4004 this.lineBreak();
4005 if (breakSize > 1)
4006 for (let point of this.points)
4007 if (point.node == node && point.pos > this.text.length)
4008 point.pos -= breakSize - 1;
4009 off = nextBreak + breakSize;
4010 }
4011 }
4012 readNode(node) {
4013 let tile = Tile.get(node);
4014 let fromView = tile && tile.overrideDOMText;
4015 if (fromView != null) {
4016 this.findPointInside(node, fromView.length);
4017 for (let i = fromView.iter(); !i.next().done;) {
4018 if (i.lineBreak)
4019 this.lineBreak();
4020 else
4021 this.append(i.value);
4022 }
4023 }
4024 else if (node.nodeType == 3) {
4025 this.readTextNode(node);
4026 }
4027 else if (node.nodeName == "BR") {
4028 if (node.nextSibling)
4029 this.lineBreak();
4030 }
4031 else if (node.nodeType == 1) {
4032 this.readRange(node.firstChild, null);
4033 }
4034 }
4035 findPointBefore(node, next) {
4036 for (let point of this.points)
4037 if (point.node == node && node.childNodes[point.offset] == next)
4038 point.pos = this.text.length;
4039 }
4040 findPointInside(node, length) {
4041 for (let point of this.points)
4042 if (node.nodeType == 3 ? point.node == node : node.contains(point.node))
4043 point.pos = this.text.length + (isAtEnd(node, point.node, point.offset) ? length : 0);
4044 }
4045}
4046function isAtEnd(parent, node, offset) {
4047 for (;;) {
4048 if (!node || offset < maxOffset(node))
4049 return false;
4050 if (node == parent)
4051 return true;
4052 offset = domIndex(node) + 1;
4053 node = node.parentNode;
4054 }
4055}
4056function isEmptyToEnd(node, end) {
4057 let widgets;
4058 for (;; node = node.nextSibling) {
4059 if (node == end || !node)
4060 break;
4061 let view = Tile.get(node);
4062 if (!(view === null || view === void 0 ? void 0 : view.isWidget()))
4063 return false;
4064 if (view)
4065 (widgets || (widgets = [])).push(view);
4066 }
4067 if (widgets)
4068 for (let w of widgets) {
4069 let override = w.overrideDOMText;
4070 if (override === null || override === void 0 ? void 0 : override.length)
4071 return false;
4072 }
4073 return true;
4074}
4075class DOMPoint {
4076 constructor(node, offset) {
4077 this.node = node;
4078 this.offset = offset;
4079 this.pos = -1;
4080 }
4081}
4082
4083class DOMChange {
4084 constructor(view, start, end, typeOver) {
4085 this.typeOver = typeOver;
4086 this.bounds = null;
4087 this.text = "";
4088 this.domChanged = start > -1;
4089 let { impreciseHead: iHead, impreciseAnchor: iAnchor } = view.docView;
4090 if (view.state.readOnly && start > -1) {
4091 // Ignore changes when the editor is read-only
4092 this.newSel = null;
4093 }
4094 else if (start > -1 && (this.bounds = domBoundsAround(view.docView.tile, start, end, 0))) {
4095 let selPoints = iHead || iAnchor ? [] : selectionPoints(view);
4096 let reader = new DOMReader(selPoints, view);
4097 reader.readRange(this.bounds.startDOM, this.bounds.endDOM);
4098 this.text = reader.text;
4099 this.newSel = selectionFromPoints(selPoints, this.bounds.from);
4100 }
4101 else {
4102 let domSel = view.observer.selectionRange;
4103 let head = iHead && iHead.node == domSel.focusNode && iHead.offset == domSel.focusOffset ||
4104 !contains(view.contentDOM, domSel.focusNode)
4105 ? view.state.selection.main.head
4106 : view.docView.posFromDOM(domSel.focusNode, domSel.focusOffset);
4107 let anchor = iAnchor && iAnchor.node == domSel.anchorNode && iAnchor.offset == domSel.anchorOffset ||
4108 !contains(view.contentDOM, domSel.anchorNode)
4109 ? view.state.selection.main.anchor
4110 : view.docView.posFromDOM(domSel.anchorNode, domSel.anchorOffset);
4111 // iOS will refuse to select the block gaps when doing
4112 // select-all.
4113 // Chrome will put the selection *inside* them, confusing
4114 // posFromDOM
4115 let vp = view.viewport;
4116 if ((browser.ios || browser.chrome) && view.state.selection.main.empty && head != anchor &&
4117 (vp.from > 0 || vp.to < view.state.doc.length)) {
4118 let from = Math.min(head, anchor), to = Math.max(head, anchor);
4119 let offFrom = vp.from - from, offTo = vp.to - to;
4120 if ((offFrom == 0 || offFrom == 1 || from == 0) && (offTo == 0 || offTo == -1 || to == view.state.doc.length)) {
4121 head = 0;
4122 anchor = view.state.doc.length;
4123 }
4124 }
4125 if (view.inputState.composing > -1 && view.state.selection.ranges.length > 1)
4126 this.newSel = view.state.selection.replaceRange(state.EditorSelection.range(anchor, head));
4127 else
4128 this.newSel = state.EditorSelection.single(anchor, head);
4129 }
4130 }
4131}
4132function domBoundsAround(tile, from, to, offset) {
4133 if (tile.isComposite()) {
4134 let fromI = -1, fromStart = -1, toI = -1, toEnd = -1;
4135 for (let i = 0, pos = offset, prevEnd = offset; i < tile.children.length; i++) {
4136 let child = tile.children[i], end = pos + child.length;
4137 if (pos < from && end > to)
4138 return domBoundsAround(child, from, to, pos);
4139 if (end >= from && fromI == -1) {
4140 fromI = i;
4141 fromStart = pos;
4142 }
4143 if (pos > to && child.dom.parentNode == tile.dom) {
4144 toI = i;
4145 toEnd = prevEnd;
4146 break;
4147 }
4148 prevEnd = end;
4149 pos = end + child.breakAfter;
4150 }
4151 return { from: fromStart, to: toEnd < 0 ? offset + tile.length : toEnd,
4152 startDOM: (fromI ? tile.children[fromI - 1].dom.nextSibling : null) || tile.dom.firstChild,
4153 endDOM: toI < tile.children.length && toI >= 0 ? tile.children[toI].dom : null };
4154 }
4155 else if (tile.isText()) {
4156 return { from: offset, to: offset + tile.length, startDOM: tile.dom, endDOM: tile.dom.nextSibling };
4157 }
4158 else {
4159 return null;
4160 }
4161}
4162function applyDOMChange(view, domChange) {
4163 let change;
4164 let { newSel } = domChange, { state: state$1 } = view, sel = state$1.selection.main;
4165 let lastKey = view.inputState.lastKeyTime > Date.now() - 100 ? view.inputState.lastKeyCode : -1;
4166 if (domChange.bounds) {
4167 let { from, to } = domChange.bounds;
4168 let preferredPos = sel.from, preferredSide = null;
4169 // Prefer anchoring to end when Backspace is pressed (or, on
4170 // Android, when something was deleted)
4171 if (lastKey === 8 || browser.android && domChange.text.length < to - from) {
4172 preferredPos = sel.to;
4173 preferredSide = "end";
4174 }
4175 let cmp = state$1.doc.sliceString(from, to, LineBreakPlaceholder), selEnd, diff;
4176 if (!sel.empty && sel.from >= from && sel.to <= to && (domChange.typeOver || cmp != domChange.text) &&
4177 cmp.slice(0, sel.from - from) == domChange.text.slice(0, sel.from - from) &&
4178 cmp.slice(sel.to - from) == domChange.text.slice(selEnd = domChange.text.length - (cmp.length - (sel.to - from)))) {
4179 // This looks like a selection replacement
4180 change = { from: sel.from, to: sel.to,
4181 insert: state.Text.of(domChange.text.slice(sel.from - from, selEnd).split(LineBreakPlaceholder)) };
4182 }
4183 else if (diff = findDiff(cmp, domChange.text, preferredPos - from, preferredSide)) {
4184 // Chrome inserts two newlines when pressing shift-enter at the
4185 // end of a line. DomChange drops one of those.
4186 if (browser.chrome && lastKey == 13 &&
4187 diff.toB == diff.from + 2 && domChange.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
4188 diff.toB--;
4189 change = { from: from + diff.from, to: from + diff.toA,
4190 insert: state.Text.of(domChange.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
4191 }
4192 }
4193 else if (newSel && (!view.hasFocus && state$1.facet(editable) || sameSelPos(newSel, sel))) {
4194 newSel = null;
4195 }
4196 if (!change && !newSel)
4197 return false;
4198 if ((browser.mac || browser.android) && change && change.from == change.to && change.from == sel.head - 1 &&
4199 /^\. ?$/.test(change.insert.toString()) && view.contentDOM.getAttribute("autocorrect") == "off") {
4200 // Detect insert-period-on-double-space Mac and Android behavior,
4201 // and transform it into a regular space insert.
4202 if (newSel && change.insert.length == 2)
4203 newSel = state.EditorSelection.single(newSel.main.anchor - 1, newSel.main.head - 1);
4204 change = { from: change.from, to: change.to, insert: state.Text.of([change.insert.toString().replace(".", " ")]) };
4205 }
4206 else if (state$1.doc.lineAt(sel.from).to < sel.to && view.docView.lineHasWidget(sel.to) &&
4207 view.inputState.insertingTextAt > Date.now() - 50) {
4208 // For a cross-line insertion, Chrome and Safari will crudely take
4209 // the text of the line after the selection, flattening any
4210 // widgets, and move it into the joined line. This tries to detect
4211 // such a situation, and replaces the change with a selection
4212 // replace of the text provided by the beforeinput event.
4213 change = {
4214 from: sel.from, to: sel.to,
4215 insert: state$1.toText(view.inputState.insertingText)
4216 };
4217 }
4218 else if (browser.chrome && change && change.from == change.to && change.from == sel.head &&
4219 change.insert.toString() == "\n " && view.lineWrapping) {
4220 // In Chrome, if you insert a space at the start of a wrapped
4221 // line, it will actually insert a newline and a space, causing a
4222 // bogus new line to be created in CodeMirror (#968)
4223 if (newSel)
4224 newSel = state.EditorSelection.single(newSel.main.anchor - 1, newSel.main.head - 1);
4225 change = { from: sel.from, to: sel.to, insert: state.Text.of([" "]) };
4226 }
4227 if (change) {
4228 return applyDOMChangeInner(view, change, newSel, lastKey);
4229 }
4230 else if (newSel && !sameSelPos(newSel, sel)) {
4231 let scrollIntoView = false, userEvent = "select";
4232 if (view.inputState.lastSelectionTime > Date.now() - 50) {
4233 if (view.inputState.lastSelectionOrigin == "select")
4234 scrollIntoView = true;
4235 userEvent = view.inputState.lastSelectionOrigin;
4236 if (userEvent == "select.pointer")
4237 newSel = skipAtomsForSelection(state$1.facet(atomicRanges).map(f => f(view)), newSel);
4238 }
4239 view.dispatch({ selection: newSel, scrollIntoView, userEvent });
4240 return true;
4241 }
4242 else {
4243 return false;
4244 }
4245}
4246function applyDOMChangeInner(view, change, newSel, lastKey = -1) {
4247 if (browser.ios && view.inputState.flushIOSKey(change))
4248 return true;
4249 let sel = view.state.selection.main;
4250 // Android browsers don't fire reasonable key events for enter,
4251 // backspace, or delete. So this detects changes that look like
4252 // they're caused by those keys, and reinterprets them as key
4253 // events. (Some of these keys are also handled by beforeinput
4254 // events and the pendingAndroidKey mechanism, but that's not
4255 // reliable in all situations.)
4256 if (browser.android &&
4257 ((change.to == sel.to &&
4258 // GBoard will sometimes remove a space it just inserted
4259 // after a completion when you press enter
4260 (change.from == sel.from || change.from == sel.from - 1 && view.state.sliceDoc(change.from, sel.from) == " ") &&
4261 change.insert.length == 1 && change.insert.lines == 2 &&
4262 dispatchKey(view.contentDOM, "Enter", 13)) ||
4263 ((change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 ||
4264 lastKey == 8 && change.insert.length < change.to - change.from && change.to > sel.head) &&
4265 dispatchKey(view.contentDOM, "Backspace", 8)) ||
4266 (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
4267 dispatchKey(view.contentDOM, "Delete", 46))))
4268 return true;
4269 let text = change.insert.toString();
4270 if (view.inputState.composing >= 0)
4271 view.inputState.composing++;
4272 let defaultTr;
4273 let defaultInsert = () => defaultTr || (defaultTr = applyDefaultInsert(view, change, newSel));
4274 if (!view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text, defaultInsert)))
4275 view.dispatch(defaultInsert());
4276 return true;
4277}
4278function applyDefaultInsert(view, change, newSel) {
4279 let tr, startState = view.state, sel = startState.selection.main, inAtomic = -1;
4280 if (change.from == change.to && change.from < sel.from || change.from > sel.to) {
4281 let side = change.from < sel.from ? -1 : 1, pos = side < 0 ? sel.from : sel.to;
4282 let moved = skipAtomicRanges(startState.facet(atomicRanges).map(f => f(view)), pos, side);
4283 if (change.from == moved)
4284 inAtomic = moved;
4285 }
4286 if (inAtomic > -1) {
4287 tr = {
4288 changes: change,
4289 selection: state.EditorSelection.cursor(change.from + change.insert.length, -1)
4290 };
4291 }
4292 else if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
4293 (!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
4294 view.inputState.composing < 0) {
4295 let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
4296 let after = sel.to > change.to ? startState.sliceDoc(change.to, sel.to) : "";
4297 tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
4298 }
4299 else {
4300 let changes = startState.changes(change);
4301 let mainSel = newSel && newSel.main.to <= changes.newLength ? newSel.main : undefined;
4302 // Try to apply a composition change to all cursors
4303 if (startState.selection.ranges.length > 1 && (view.inputState.composing >= 0 || view.inputState.compositionPendingChange) &&
4304 change.to <= sel.to + 10 && change.to >= sel.to - 10) {
4305 let replaced = view.state.sliceDoc(change.from, change.to);
4306 let compositionRange, composition = newSel && findCompositionNode(view, newSel.main.head);
4307 if (composition) {
4308 let dLen = change.insert.length - (change.to - change.from);
4309 compositionRange = { from: composition.from, to: composition.to - dLen };
4310 }
4311 else {
4312 compositionRange = view.state.doc.lineAt(sel.head);
4313 }
4314 let offset = sel.to - change.to;
4315 tr = startState.changeByRange(range => {
4316 if (range.from == sel.from && range.to == sel.to)
4317 return { changes, range: mainSel || range.map(changes) };
4318 let to = range.to - offset, from = to - replaced.length;
4319 if (view.state.sliceDoc(from, to) != replaced ||
4320 // Unfortunately, there's no way to make multiple
4321 // changes in the same node work without aborting
4322 // composition, so cursors in the composition range are
4323 // ignored.
4324 to >= compositionRange.from && from <= compositionRange.to)
4325 return { range };
4326 let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
4327 return {
4328 changes: rangeChanges,
4329 range: !mainSel ? range.map(rangeChanges) :
4330 state.EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
4331 };
4332 });
4333 }
4334 else {
4335 tr = {
4336 changes,
4337 selection: mainSel && startState.selection.replaceRange(mainSel)
4338 };
4339 }
4340 }
4341 let userEvent = "input.type";
4342 if (view.composing ||
4343 view.inputState.compositionPendingChange && view.inputState.compositionEndedAt > Date.now() - 50) {
4344 view.inputState.compositionPendingChange = false;
4345 userEvent += ".compose";
4346 if (view.inputState.compositionFirstChange) {
4347 userEvent += ".start";
4348 view.inputState.compositionFirstChange = false;
4349 }
4350 }
4351 return startState.update(tr, { userEvent, scrollIntoView: true });
4352}
4353function findDiff(a, b, preferredPos, preferredSide) {
4354 let minLen = Math.min(a.length, b.length);
4355 let from = 0;
4356 while (from < minLen && a.charCodeAt(from) == b.charCodeAt(from))
4357 from++;
4358 if (from == minLen && a.length == b.length)
4359 return null;
4360 let toA = a.length, toB = b.length;
4361 while (toA > 0 && toB > 0 && a.charCodeAt(toA - 1) == b.charCodeAt(toB - 1)) {
4362 toA--;
4363 toB--;
4364 }
4365 if (preferredSide == "end") {
4366 let adjust = Math.max(0, from - Math.min(toA, toB));
4367 preferredPos -= toA + adjust - from;
4368 }
4369 if (toA < from && a.length < b.length) {
4370 let move = preferredPos <= from && preferredPos >= toA ? from - preferredPos : 0;
4371 from -= move;
4372 toB = from + (toB - toA);
4373 toA = from;
4374 }
4375 else if (toB < from) {
4376 let move = preferredPos <= from && preferredPos >= toB ? from - preferredPos : 0;
4377 from -= move;
4378 toA = from + (toA - toB);
4379 toB = from;
4380 }
4381 return { from, toA, toB };
4382}
4383function selectionPoints(view) {
4384 let result = [];
4385 if (view.root.activeElement != view.contentDOM)
4386 return result;
4387 let { anchorNode, anchorOffset, focusNode, focusOffset } = view.observer.selectionRange;
4388 if (anchorNode) {
4389 result.push(new DOMPoint(anchorNode, anchorOffset));
4390 if (focusNode != anchorNode || focusOffset != anchorOffset)
4391 result.push(new DOMPoint(focusNode, focusOffset));
4392 }
4393 return result;
4394}
4395function selectionFromPoints(points, base) {
4396 if (points.length == 0)
4397 return null;
4398 let anchor = points[0].pos, head = points.length == 2 ? points[1].pos : anchor;
4399 return anchor > -1 && head > -1 ? state.EditorSelection.single(anchor + base, head + base) : null;
4400}
4401function sameSelPos(selection, range) {
4402 return range.head == selection.main.head && range.anchor == selection.main.anchor;
4403}
4404
4405class InputState {
4406 setSelectionOrigin(origin) {
4407 this.lastSelectionOrigin = origin;
4408 this.lastSelectionTime = Date.now();
4409 }
4410 constructor(view) {
4411 this.view = view;
4412 this.lastKeyCode = 0;
4413 this.lastKeyTime = 0;
4414 this.lastTouchTime = 0;
4415 this.lastFocusTime = 0;
4416 this.lastScrollTop = 0;
4417 this.lastScrollLeft = 0;
4418 this.lastWheelEvent = 0;
4419 // On iOS, some keys need to have their default behavior happen
4420 // (after which we retroactively handle them and reset the DOM) to
4421 // avoid messing up the virtual keyboard state.
4422 this.pendingIOSKey = undefined;
4423 /**
4424 When enabled (>-1), tab presses are not given to key handlers,
4425 leaving the browser's default behavior. If >0, the mode expires
4426 at that timestamp, and any other keypress clears it.
4427 Esc enables temporary tab focus mode for two seconds when not
4428 otherwise handled.
4429 */
4430 this.tabFocusMode = -1;
4431 this.lastSelectionOrigin = null;
4432 this.lastSelectionTime = 0;
4433 this.lastContextMenu = 0;
4434 this.scrollHandlers = [];
4435 this.handlers = Object.create(null);
4436 // -1 means not in a composition. Otherwise, this counts the number
4437 // of changes made during the composition. The count is used to
4438 // avoid treating the start state of the composition, before any
4439 // changes have been made, as part of the composition.
4440 this.composing = -1;
4441 // Tracks whether the next change should be marked as starting the
4442 // composition (null means no composition, true means next is the
4443 // first, false means first has already been marked for this
4444 // composition)
4445 this.compositionFirstChange = null;
4446 // End time of the previous composition
4447 this.compositionEndedAt = 0;
4448 // Used in a kludge to detect when an Enter keypress should be
4449 // considered part of the composition on Safari, which fires events
4450 // in the wrong order
4451 this.compositionPendingKey = false;
4452 // Used to categorize changes as part of a composition, even when
4453 // the mutation events fire shortly after the compositionend event
4454 this.compositionPendingChange = false;
4455 // Set by beforeinput, used in DOM change reader
4456 this.insertingText = "";
4457 this.insertingTextAt = 0;
4458 this.mouseSelection = null;
4459 // When a drag from the editor is active, this points at the range
4460 // being dragged.
4461 this.draggedContent = null;
4462 this.handleEvent = this.handleEvent.bind(this);
4463 this.notifiedFocused = view.hasFocus;
4464 // On Safari adding an input event handler somehow prevents an
4465 // issue where the composition vanishes when you press enter.
4466 if (browser.safari)
4467 view.contentDOM.addEventListener("input", () => null);
4468 if (browser.gecko)
4469 firefoxCopyCutHack(view.contentDOM.ownerDocument);
4470 }
4471 handleEvent(event) {
4472 if (!eventBelongsToEditor(this.view, event) || this.ignoreDuringComposition(event))
4473 return;
4474 if (event.type == "keydown" && this.keydown(event))
4475 return;
4476 if (this.view.updateState != 0 /* UpdateState.Idle */)
4477 Promise.resolve().then(() => this.runHandlers(event.type, event));
4478 else
4479 this.runHandlers(event.type, event);
4480 }
4481 runHandlers(type, event) {
4482 let handlers = this.handlers[type];
4483 if (handlers) {
4484 for (let observer of handlers.observers)
4485 observer(this.view, event);
4486 for (let handler of handlers.handlers) {
4487 if (event.defaultPrevented)
4488 break;
4489 if (handler(this.view, event)) {
4490 event.preventDefault();
4491 break;
4492 }
4493 }
4494 }
4495 }
4496 ensureHandlers(plugins) {
4497 let handlers = computeHandlers(plugins), prev = this.handlers, dom = this.view.contentDOM;
4498 for (let type in handlers)
4499 if (type != "scroll") {
4500 let passive = !handlers[type].handlers.length;
4501 let exists = prev[type];
4502 if (exists && passive != !exists.handlers.length) {
4503 dom.removeEventListener(type, this.handleEvent);
4504 exists = null;
4505 }
4506 if (!exists)
4507 dom.addEventListener(type, this.handleEvent, { passive });
4508 }
4509 for (let type in prev)
4510 if (type != "scroll" && !handlers[type])
4511 dom.removeEventListener(type, this.handleEvent);
4512 this.handlers = handlers;
4513 }
4514 keydown(event) {
4515 // Must always run, even if a custom handler handled the event
4516 this.lastKeyCode = event.keyCode;
4517 this.lastKeyTime = Date.now();
4518 if (event.keyCode == 9 && this.tabFocusMode > -1 && (!this.tabFocusMode || Date.now() <= this.tabFocusMode))
4519 return true;
4520 if (this.tabFocusMode > 0 && event.keyCode != 27 && modifierCodes.indexOf(event.keyCode) < 0)
4521 this.tabFocusMode = -1;
4522 // Chrome for Android usually doesn't fire proper key events, but
4523 // occasionally does, usually surrounded by a bunch of complicated
4524 // composition changes. When an enter or backspace key event is
4525 // seen, hold off on handling DOM events for a bit, and then
4526 // dispatch it.
4527 if (browser.android && browser.chrome && !event.synthetic &&
4528 (event.keyCode == 13 || event.keyCode == 8)) {
4529 this.view.observer.delayAndroidKey(event.key, event.keyCode);
4530 return true;
4531 }
4532 // Preventing the default behavior of Enter on iOS makes the
4533 // virtual keyboard get stuck in the wrong (lowercase)
4534 // state. So we let it go through, and then, in
4535 // applyDOMChange, notify key handlers of it and reset to
4536 // the state they produce.
4537 let pending;
4538 if (browser.ios && !event.synthetic && !event.altKey && !event.metaKey &&
4539 ((pending = PendingKeys.find(key => key.keyCode == event.keyCode)) && !event.ctrlKey ||
4540 EmacsyPendingKeys.indexOf(event.key) > -1 && event.ctrlKey && !event.shiftKey)) {
4541 this.pendingIOSKey = pending || event;
4542 setTimeout(() => this.flushIOSKey(), 250);
4543 return true;
4544 }
4545 if (event.keyCode != 229)
4546 this.view.observer.forceFlush();
4547 return false;
4548 }
4549 flushIOSKey(change) {
4550 let key = this.pendingIOSKey;
4551 if (!key)
4552 return false;
4553 // This looks like an autocorrection before Enter
4554 if (key.key == "Enter" && change && change.from < change.to && /^\S+$/.test(change.insert.toString()))
4555 return false;
4556 this.pendingIOSKey = undefined;
4557 return dispatchKey(this.view.contentDOM, key.key, key.keyCode, key instanceof KeyboardEvent ? key : undefined);
4558 }
4559 ignoreDuringComposition(event) {
4560 if (!/^key/.test(event.type) || event.synthetic)
4561 return false;
4562 if (this.composing > 0)
4563 return true;
4564 // See https://www.stum.de/2016/06/24/handling-ime-events-in-javascript/.
4565 // On some input method editors (IMEs), the Enter key is used to
4566 // confirm character selection. On Safari, when Enter is pressed,
4567 // compositionend and keydown events are sometimes emitted in the
4568 // wrong order. The key event should still be ignored, even when
4569 // it happens after the compositionend event.
4570 if (browser.safari && !browser.ios && this.compositionPendingKey && Date.now() - this.compositionEndedAt < 100) {
4571 this.compositionPendingKey = false;
4572 return true;
4573 }
4574 return false;
4575 }
4576 startMouseSelection(mouseSelection) {
4577 if (this.mouseSelection)
4578 this.mouseSelection.destroy();
4579 this.mouseSelection = mouseSelection;
4580 }
4581 update(update) {
4582 this.view.observer.update(update);
4583 if (this.mouseSelection)
4584 this.mouseSelection.update(update);
4585 if (this.draggedContent && update.docChanged)
4586 this.draggedContent = this.draggedContent.map(update.changes);
4587 if (update.transactions.length)
4588 this.lastKeyCode = this.lastSelectionTime = 0;
4589 }
4590 destroy() {
4591 if (this.mouseSelection)
4592 this.mouseSelection.destroy();
4593 }
4594}
4595function bindHandler(plugin, handler) {
4596 return (view, event) => {
4597 try {
4598 return handler.call(plugin, event, view);
4599 }
4600 catch (e) {
4601 logException(view.state, e);
4602 }
4603 };
4604}
4605function computeHandlers(plugins) {
4606 let result = Object.create(null);
4607 function record(type) {
4608 return result[type] || (result[type] = { observers: [], handlers: [] });
4609 }
4610 for (let plugin of plugins) {
4611 let spec = plugin.spec, handlers = spec && spec.plugin.domEventHandlers, observers = spec && spec.plugin.domEventObservers;
4612 if (handlers)
4613 for (let type in handlers) {
4614 let f = handlers[type];
4615 if (f)
4616 record(type).handlers.push(bindHandler(plugin.value, f));
4617 }
4618 if (observers)
4619 for (let type in observers) {
4620 let f = observers[type];
4621 if (f)
4622 record(type).observers.push(bindHandler(plugin.value, f));
4623 }
4624 }
4625 for (let type in handlers)
4626 record(type).handlers.push(handlers[type]);
4627 for (let type in observers)
4628 record(type).observers.push(observers[type]);
4629 return result;
4630}
4631const PendingKeys = [
4632 { key: "Backspace", keyCode: 8, inputType: "deleteContentBackward" },
4633 { key: "Enter", keyCode: 13, inputType: "insertParagraph" },
4634 { key: "Enter", keyCode: 13, inputType: "insertLineBreak" },
4635 { key: "Delete", keyCode: 46, inputType: "deleteContentForward" }
4636];
4637const EmacsyPendingKeys = "dthko";
4638// Key codes for modifier keys
4639const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
4640const dragScrollMargin = 6;
4641function dragScrollSpeed(dist) {
4642 return Math.max(0, dist) * 0.7 + 8;
4643}
4644function dist(a, b) {
4645 return Math.max(Math.abs(a.clientX - b.clientX), Math.abs(a.clientY - b.clientY));
4646}
4647class MouseSelection {
4648 constructor(view, startEvent, style, mustSelect) {
4649 this.view = view;
4650 this.startEvent = startEvent;
4651 this.style = style;
4652 this.mustSelect = mustSelect;
4653 this.scrollSpeed = { x: 0, y: 0 };
4654 this.scrolling = -1;
4655 this.lastEvent = startEvent;
4656 this.scrollParents = scrollableParents(view.contentDOM);
4657 this.atoms = view.state.facet(atomicRanges).map(f => f(view));
4658 let doc = view.contentDOM.ownerDocument;
4659 doc.addEventListener("mousemove", this.move = this.move.bind(this));
4660 doc.addEventListener("mouseup", this.up = this.up.bind(this));
4661 this.extend = startEvent.shiftKey;
4662 this.multiple = view.state.facet(state.EditorState.allowMultipleSelections) && addsSelectionRange(view, startEvent);
4663 this.dragging = isInPrimarySelection(view, startEvent) && getClickType(startEvent) == 1 ? null : false;
4664 }
4665 start(event) {
4666 // When clicking outside of the selection, immediately apply the
4667 // effect of starting the selection
4668 if (this.dragging === false)
4669 this.select(event);
4670 }
4671 move(event) {
4672 if (event.buttons == 0)
4673 return this.destroy();
4674 if (this.dragging || this.dragging == null && dist(this.startEvent, event) < 10)
4675 return;
4676 this.select(this.lastEvent = event);
4677 let sx = 0, sy = 0;
4678 let left = 0, top = 0, right = this.view.win.innerWidth, bottom = this.view.win.innerHeight;
4679 if (this.scrollParents.x)
4680 ({ left, right } = this.scrollParents.x.getBoundingClientRect());
4681 if (this.scrollParents.y)
4682 ({ top, bottom } = this.scrollParents.y.getBoundingClientRect());
4683 let margins = getScrollMargins(this.view);
4684 if (event.clientX - margins.left <= left + dragScrollMargin)
4685 sx = -dragScrollSpeed(left - event.clientX);
4686 else if (event.clientX + margins.right >= right - dragScrollMargin)
4687 sx = dragScrollSpeed(event.clientX - right);
4688 if (event.clientY - margins.top <= top + dragScrollMargin)
4689 sy = -dragScrollSpeed(top - event.clientY);
4690 else if (event.clientY + margins.bottom >= bottom - dragScrollMargin)
4691 sy = dragScrollSpeed(event.clientY - bottom);
4692 this.setScrollSpeed(sx, sy);
4693 }
4694 up(event) {
4695 if (this.dragging == null)
4696 this.select(this.lastEvent);
4697 if (!this.dragging)
4698 event.preventDefault();
4699 this.destroy();
4700 }
4701 destroy() {
4702 this.setScrollSpeed(0, 0);
4703 let doc = this.view.contentDOM.ownerDocument;
4704 doc.removeEventListener("mousemove", this.move);
4705 doc.removeEventListener("mouseup", this.up);
4706 this.view.inputState.mouseSelection = this.view.inputState.draggedContent = null;
4707 }
4708 setScrollSpeed(sx, sy) {
4709 this.scrollSpeed = { x: sx, y: sy };
4710 if (sx || sy) {
4711 if (this.scrolling < 0)
4712 this.scrolling = setInterval(() => this.scroll(), 50);
4713 }
4714 else if (this.scrolling > -1) {
4715 clearInterval(this.scrolling);
4716 this.scrolling = -1;
4717 }
4718 }
4719 scroll() {
4720 let { x, y } = this.scrollSpeed;
4721 if (x && this.scrollParents.x) {
4722 this.scrollParents.x.scrollLeft += x;
4723 x = 0;
4724 }
4725 if (y && this.scrollParents.y) {
4726 this.scrollParents.y.scrollTop += y;
4727 y = 0;
4728 }
4729 if (x || y)
4730 this.view.win.scrollBy(x, y);
4731 if (this.dragging === false)
4732 this.select(this.lastEvent);
4733 }
4734 select(event) {
4735 let { view } = this, selection = skipAtomsForSelection(this.atoms, this.style.get(event, this.extend, this.multiple));
4736 if (this.mustSelect || !selection.eq(view.state.selection, this.dragging === false))
4737 this.view.dispatch({
4738 selection,
4739 userEvent: "select.pointer"
4740 });
4741 this.mustSelect = false;
4742 }
4743 update(update) {
4744 if (update.transactions.some(tr => tr.isUserEvent("input.type")))
4745 this.destroy();
4746 else if (this.style.update(update))
4747 setTimeout(() => this.select(this.lastEvent), 20);
4748 }
4749}
4750function addsSelectionRange(view, event) {
4751 let facet = view.state.facet(clickAddsSelectionRange);
4752 return facet.length ? facet[0](event) : browser.mac ? event.metaKey : event.ctrlKey;
4753}
4754function dragMovesSelection(view, event) {
4755 let facet = view.state.facet(dragMovesSelection$1);
4756 return facet.length ? facet[0](event) : browser.mac ? !event.altKey : !event.ctrlKey;
4757}
4758function isInPrimarySelection(view, event) {
4759 let { main } = view.state.selection;
4760 if (main.empty)
4761 return false;
4762 // On boundary clicks, check whether the coordinates are inside the
4763 // selection's client rectangles
4764 let sel = getSelection(view.root);
4765 if (!sel || sel.rangeCount == 0)
4766 return true;
4767 let rects = sel.getRangeAt(0).getClientRects();
4768 for (let i = 0; i < rects.length; i++) {
4769 let rect = rects[i];
4770 if (rect.left <= event.clientX && rect.right >= event.clientX &&
4771 rect.top <= event.clientY && rect.bottom >= event.clientY)
4772 return true;
4773 }
4774 return false;
4775}
4776function eventBelongsToEditor(view, event) {
4777 if (!event.bubbles)
4778 return true;
4779 if (event.defaultPrevented)
4780 return false;
4781 for (let node = event.target, tile; node != view.contentDOM; node = node.parentNode)
4782 if (!node || node.nodeType == 11 ||
4783 ((tile = Tile.get(node)) && tile.isWidget() && !tile.isHidden && tile.widget.ignoreEvent(event)))
4784 return false;
4785 return true;
4786}
4787const handlers = Object.create(null);
4788const observers = Object.create(null);
4789// This is very crude, but unfortunately both these browsers _pretend_
4790// that they have a clipboard API—all the objects and methods are
4791// there, they just don't work, and they are hard to test.
4792const brokenClipboardAPI = (browser.ie && browser.ie_version < 15) ||
4793 (browser.ios && browser.webkit_version < 604);
4794function capturePaste(view) {
4795 let parent = view.dom.parentNode;
4796 if (!parent)
4797 return;
4798 let target = parent.appendChild(document.createElement("textarea"));
4799 target.style.cssText = "position: fixed; left: -10000px; top: 10px";
4800 target.focus();
4801 setTimeout(() => {
4802 view.focus();
4803 target.remove();
4804 doPaste(view, target.value);
4805 }, 50);
4806}
4807function textFilter(state, facet, text) {
4808 for (let filter of state.facet(facet))
4809 text = filter(text, state);
4810 return text;
4811}
4812function doPaste(view, input) {
4813 input = textFilter(view.state, clipboardInputFilter, input);
4814 let { state: state$1 } = view, changes, i = 1, text = state$1.toText(input);
4815 let byLine = text.lines == state$1.selection.ranges.length;
4816 let linewise = lastLinewiseCopy != null && state$1.selection.ranges.every(r => r.empty) && lastLinewiseCopy == text.toString();
4817 if (linewise) {
4818 let lastLine = -1;
4819 changes = state$1.changeByRange(range => {
4820 let line = state$1.doc.lineAt(range.from);
4821 if (line.from == lastLine)
4822 return { range };
4823 lastLine = line.from;
4824 let insert = state$1.toText((byLine ? text.line(i++).text : input) + state$1.lineBreak);
4825 return { changes: { from: line.from, insert },
4826 range: state.EditorSelection.cursor(range.from + insert.length) };
4827 });
4828 }
4829 else if (byLine) {
4830 changes = state$1.changeByRange(range => {
4831 let line = text.line(i++);
4832 return { changes: { from: range.from, to: range.to, insert: line.text },
4833 range: state.EditorSelection.cursor(range.from + line.length) };
4834 });
4835 }
4836 else {
4837 changes = state$1.replaceSelection(text);
4838 }
4839 view.dispatch(changes, {
4840 userEvent: "input.paste",
4841 scrollIntoView: true
4842 });
4843}
4844observers.scroll = view => {
4845 view.inputState.lastScrollTop = view.scrollDOM.scrollTop;
4846 view.inputState.lastScrollLeft = view.scrollDOM.scrollLeft;
4847};
4848observers.wheel = observers.mousewheel = view => {
4849 view.inputState.lastWheelEvent = Date.now();
4850};
4851handlers.keydown = (view, event) => {
4852 view.inputState.setSelectionOrigin("select");
4853 if (event.keyCode == 27 && view.inputState.tabFocusMode != 0)
4854 view.inputState.tabFocusMode = Date.now() + 2000;
4855 return false;
4856};
4857observers.touchstart = (view, e) => {
4858 view.inputState.lastTouchTime = Date.now();
4859 view.inputState.setSelectionOrigin("select.pointer");
4860};
4861observers.touchmove = view => {
4862 view.inputState.setSelectionOrigin("select.pointer");
4863};
4864handlers.mousedown = (view, event) => {
4865 view.observer.flush();
4866 if (view.inputState.lastTouchTime > Date.now() - 2000)
4867 return false; // Ignore touch interaction
4868 let style = null;
4869 for (let makeStyle of view.state.facet(mouseSelectionStyle)) {
4870 style = makeStyle(view, event);
4871 if (style)
4872 break;
4873 }
4874 if (!style && event.button == 0)
4875 style = basicMouseSelection(view, event);
4876 if (style) {
4877 let mustFocus = !view.hasFocus;
4878 view.inputState.startMouseSelection(new MouseSelection(view, event, style, mustFocus));
4879 if (mustFocus)
4880 view.observer.ignore(() => {
4881 focusPreventScroll(view.contentDOM);
4882 let active = view.root.activeElement;
4883 if (active && !active.contains(view.contentDOM))
4884 active.blur();
4885 });
4886 let mouseSel = view.inputState.mouseSelection;
4887 if (mouseSel) {
4888 mouseSel.start(event);
4889 return mouseSel.dragging === false;
4890 }
4891 }
4892 else {
4893 view.inputState.setSelectionOrigin("select.pointer");
4894 }
4895 return false;
4896};
4897function rangeForClick(view, pos, bias, type) {
4898 if (type == 1) { // Single click
4899 return state.EditorSelection.cursor(pos, bias);
4900 }
4901 else if (type == 2) { // Double click
4902 return groupAt(view.state, pos, bias);
4903 }
4904 else { // Triple click
4905 let visual = view.docView.lineAt(pos, bias), line = view.state.doc.lineAt(visual ? visual.posAtEnd : pos);
4906 let from = visual ? visual.posAtStart : line.from, to = visual ? visual.posAtEnd : line.to;
4907 if (to < view.state.doc.length && to == line.to)
4908 to++;
4909 return state.EditorSelection.range(from, to);
4910 }
4911}
4912const BadMouseDetail = browser.ie && browser.ie_version <= 11;
4913let lastMouseDown = null, lastMouseDownCount = 0, lastMouseDownTime = 0;
4914function getClickType(event) {
4915 if (!BadMouseDetail)
4916 return event.detail;
4917 let last = lastMouseDown, lastTime = lastMouseDownTime;
4918 lastMouseDown = event;
4919 lastMouseDownTime = Date.now();
4920 return lastMouseDownCount = !last || (lastTime > Date.now() - 400 && Math.abs(last.clientX - event.clientX) < 2 &&
4921 Math.abs(last.clientY - event.clientY) < 2) ? (lastMouseDownCount + 1) % 3 : 1;
4922}
4923function basicMouseSelection(view, event) {
4924 let start = view.posAndSideAtCoords({ x: event.clientX, y: event.clientY }, false), type = getClickType(event);
4925 let startSel = view.state.selection;
4926 return {
4927 update(update) {
4928 if (update.docChanged) {
4929 start.pos = update.changes.mapPos(start.pos);
4930 startSel = startSel.map(update.changes);
4931 }
4932 },
4933 get(event, extend, multiple) {
4934 let cur = view.posAndSideAtCoords({ x: event.clientX, y: event.clientY }, false), removed;
4935 let range = rangeForClick(view, cur.pos, cur.assoc, type);
4936 if (start.pos != cur.pos && !extend) {
4937 let startRange = rangeForClick(view, start.pos, start.assoc, type);
4938 let from = Math.min(startRange.from, range.from), to = Math.max(startRange.to, range.to);
4939 range = from < range.from ? state.EditorSelection.range(from, to) : state.EditorSelection.range(to, from);
4940 }
4941 if (extend)
4942 return startSel.replaceRange(startSel.main.extend(range.from, range.to));
4943 else if (multiple && type == 1 && startSel.ranges.length > 1 && (removed = removeRangeAround(startSel, cur.pos)))
4944 return removed;
4945 else if (multiple)
4946 return startSel.addRange(range);
4947 else
4948 return state.EditorSelection.create([range]);
4949 }
4950 };
4951}
4952function removeRangeAround(sel, pos) {
4953 for (let i = 0; i < sel.ranges.length; i++) {
4954 let { from, to } = sel.ranges[i];
4955 if (from <= pos && to >= pos)
4956 return state.EditorSelection.create(sel.ranges.slice(0, i).concat(sel.ranges.slice(i + 1)), sel.mainIndex == i ? 0 : sel.mainIndex - (sel.mainIndex > i ? 1 : 0));
4957 }
4958 return null;
4959}
4960handlers.dragstart = (view, event) => {
4961 let { selection: { main: range } } = view.state;
4962 if (event.target.draggable) {
4963 let tile = view.docView.tile.nearest(event.target);
4964 if (tile && tile.isWidget()) {
4965 let from = tile.posAtStart, to = from + tile.length;
4966 if (from >= range.to || to <= range.from)
4967 range = state.EditorSelection.range(from, to);
4968 }
4969 }
4970 let { inputState } = view;
4971 if (inputState.mouseSelection)
4972 inputState.mouseSelection.dragging = true;
4973 inputState.draggedContent = range;
4974 if (event.dataTransfer) {
4975 event.dataTransfer.setData("Text", textFilter(view.state, clipboardOutputFilter, view.state.sliceDoc(range.from, range.to)));
4976 event.dataTransfer.effectAllowed = "copyMove";
4977 }
4978 return false;
4979};
4980handlers.dragend = view => {
4981 view.inputState.draggedContent = null;
4982 return false;
4983};
4984function dropText(view, event, text, direct) {
4985 text = textFilter(view.state, clipboardInputFilter, text);
4986 if (!text)
4987 return;
4988 let dropPos = view.posAtCoords({ x: event.clientX, y: event.clientY }, false);
4989 let { draggedContent } = view.inputState;
4990 let del = direct && draggedContent && dragMovesSelection(view, event)
4991 ? { from: draggedContent.from, to: draggedContent.to } : null;
4992 let ins = { from: dropPos, insert: text };
4993 let changes = view.state.changes(del ? [del, ins] : ins);
4994 view.focus();
4995 view.dispatch({
4996 changes,
4997 selection: { anchor: changes.mapPos(dropPos, -1), head: changes.mapPos(dropPos, 1) },
4998 userEvent: del ? "move.drop" : "input.drop"
4999 });
5000 view.inputState.draggedContent = null;
5001}
5002handlers.drop = (view, event) => {
5003 if (!event.dataTransfer)
5004 return false;
5005 if (view.state.readOnly)
5006 return true;
5007 let files = event.dataTransfer.files;
5008 if (files && files.length) { // For a file drop, read the file's text.
5009 let text = Array(files.length), read = 0;
5010 let finishFile = () => {
5011 if (++read == files.length)
5012 dropText(view, event, text.filter(s => s != null).join(view.state.lineBreak), false);
5013 };
5014 for (let i = 0; i < files.length; i++) {
5015 let reader = new FileReader;
5016 reader.onerror = finishFile;
5017 reader.onload = () => {
5018 if (!/[\x00-\x08\x0e-\x1f]{2}/.test(reader.result))
5019 text[i] = reader.result;
5020 finishFile();
5021 };
5022 reader.readAsText(files[i]);
5023 }
5024 return true;
5025 }
5026 else {
5027 let text = event.dataTransfer.getData("Text");
5028 if (text) {
5029 dropText(view, event, text, true);
5030 return true;
5031 }
5032 }
5033 return false;
5034};
5035handlers.paste = (view, event) => {
5036 if (view.state.readOnly)
5037 return true;
5038 view.observer.flush();
5039 let data = brokenClipboardAPI ? null : event.clipboardData;
5040 if (data) {
5041 doPaste(view, data.getData("text/plain") || data.getData("text/uri-list"));
5042 return true;
5043 }
5044 else {
5045 capturePaste(view);
5046 return false;
5047 }
5048};
5049function captureCopy(view, text) {
5050 // The extra wrapper is somehow necessary on IE/Edge to prevent the
5051 // content from being mangled when it is put onto the clipboard
5052 let parent = view.dom.parentNode;
5053 if (!parent)
5054 return;
5055 let target = parent.appendChild(document.createElement("textarea"));
5056 target.style.cssText = "position: fixed; left: -10000px; top: 10px";
5057 target.value = text;
5058 target.focus();
5059 target.selectionEnd = text.length;
5060 target.selectionStart = 0;
5061 setTimeout(() => {
5062 target.remove();
5063 view.focus();
5064 }, 50);
5065}
5066function copiedRange(state) {
5067 let content = [], ranges = [], linewise = false;
5068 for (let range of state.selection.ranges)
5069 if (!range.empty) {
5070 content.push(state.sliceDoc(range.from, range.to));
5071 ranges.push(range);
5072 }
5073 if (!content.length) {
5074 // Nothing selected, do a line-wise copy
5075 let upto = -1;
5076 for (let { from } of state.selection.ranges) {
5077 let line = state.doc.lineAt(from);
5078 if (line.number > upto) {
5079 content.push(line.text);
5080 ranges.push({ from: line.from, to: Math.min(state.doc.length, line.to + 1) });
5081 }
5082 upto = line.number;
5083 }
5084 linewise = true;
5085 }
5086 return { text: textFilter(state, clipboardOutputFilter, content.join(state.lineBreak)), ranges, linewise };
5087}
5088let lastLinewiseCopy = null;
5089handlers.copy = handlers.cut = (view, event) => {
5090 // If the DOM selection is outside this editor, don't intercept.
5091 // This happens when a parent editor (like ProseMirror) selects content that
5092 // spans multiple elements including this CodeMirror. The copy event may
5093 // bubble through CodeMirror (e.g. when CodeMirror is the first or the last
5094 // element in the selection), but we should let the parent handle it.
5095 if (!hasSelection(view.contentDOM, view.observer.selectionRange))
5096 return false;
5097 let { text, ranges, linewise } = copiedRange(view.state);
5098 if (!text && !linewise)
5099 return false;
5100 lastLinewiseCopy = linewise ? text : null;
5101 if (event.type == "cut" && !view.state.readOnly)
5102 view.dispatch({
5103 changes: ranges,
5104 scrollIntoView: true,
5105 userEvent: "delete.cut"
5106 });
5107 let data = brokenClipboardAPI ? null : event.clipboardData;
5108 if (data) {
5109 data.clearData();
5110 data.setData("text/plain", text);
5111 return true;
5112 }
5113 else {
5114 captureCopy(view, text);
5115 return false;
5116 }
5117};
5118const isFocusChange = state.Annotation.define();
5119function focusChangeTransaction(state, focus) {
5120 let effects = [];
5121 for (let getEffect of state.facet(focusChangeEffect)) {
5122 let effect = getEffect(state, focus);
5123 if (effect)
5124 effects.push(effect);
5125 }
5126 return effects.length ? state.update({ effects, annotations: isFocusChange.of(true) }) : null;
5127}
5128function updateForFocusChange(view) {
5129 setTimeout(() => {
5130 let focus = view.hasFocus;
5131 if (focus != view.inputState.notifiedFocused) {
5132 let tr = focusChangeTransaction(view.state, focus);
5133 if (tr)
5134 view.dispatch(tr);
5135 else
5136 view.update([]);
5137 }
5138 }, 10);
5139}
5140observers.focus = view => {
5141 view.inputState.lastFocusTime = Date.now();
5142 // When focusing reset the scroll position, move it back to where it was
5143 if (!view.scrollDOM.scrollTop && (view.inputState.lastScrollTop || view.inputState.lastScrollLeft)) {
5144 view.scrollDOM.scrollTop = view.inputState.lastScrollTop;
5145 view.scrollDOM.scrollLeft = view.inputState.lastScrollLeft;
5146 }
5147 updateForFocusChange(view);
5148};
5149observers.blur = view => {
5150 view.observer.clearSelectionRange();
5151 updateForFocusChange(view);
5152};
5153observers.compositionstart = observers.compositionupdate = view => {
5154 if (view.observer.editContext)
5155 return; // Composition handled by edit context
5156 if (view.inputState.compositionFirstChange == null)
5157 view.inputState.compositionFirstChange = true;
5158 if (view.inputState.composing < 0) {
5159 // FIXME possibly set a timeout to clear it again on Android
5160 view.inputState.composing = 0;
5161 }
5162};
5163observers.compositionend = view => {
5164 if (view.observer.editContext)
5165 return; // Composition handled by edit context
5166 view.inputState.composing = -1;
5167 view.inputState.compositionEndedAt = Date.now();
5168 view.inputState.compositionPendingKey = true;
5169 view.inputState.compositionPendingChange = view.observer.pendingRecords().length > 0;
5170 view.inputState.compositionFirstChange = null;
5171 if (browser.chrome && browser.android) {
5172 // Delay flushing for a bit on Android because it'll often fire a
5173 // bunch of contradictory changes in a row at end of compositon
5174 view.observer.flushSoon();
5175 }
5176 else if (view.inputState.compositionPendingChange) {
5177 // If we found pending records, schedule a flush.
5178 Promise.resolve().then(() => view.observer.flush());
5179 }
5180 else {
5181 // Otherwise, make sure that, if no changes come in soon, the
5182 // composition view is cleared.
5183 setTimeout(() => {
5184 if (view.inputState.composing < 0 && view.docView.hasComposition)
5185 view.update([]);
5186 }, 50);
5187 }
5188};
5189observers.contextmenu = view => {
5190 view.inputState.lastContextMenu = Date.now();
5191};
5192handlers.beforeinput = (view, event) => {
5193 var _a, _b;
5194 if (event.inputType == "insertText" || event.inputType == "insertCompositionText") {
5195 view.inputState.insertingText = event.data;
5196 view.inputState.insertingTextAt = Date.now();
5197 }
5198 // In EditContext mode, we must handle insertReplacementText events
5199 // directly, to make spell checking corrections work
5200 if (event.inputType == "insertReplacementText" && view.observer.editContext) {
5201 let text = (_a = event.dataTransfer) === null || _a === void 0 ? void 0 : _a.getData("text/plain"), ranges = event.getTargetRanges();
5202 if (text && ranges.length) {
5203 let r = ranges[0];
5204 let from = view.posAtDOM(r.startContainer, r.startOffset), to = view.posAtDOM(r.endContainer, r.endOffset);
5205 applyDOMChangeInner(view, { from, to, insert: view.state.toText(text) }, null);
5206 return true;
5207 }
5208 }
5209 // Because Chrome Android doesn't fire useful key events, use
5210 // beforeinput to detect backspace (and possibly enter and delete,
5211 // but those usually don't even seem to fire beforeinput events at
5212 // the moment) and fake a key event for it.
5213 //
5214 // (preventDefault on beforeinput, though supported in the spec,
5215 // seems to do nothing at all on Chrome).
5216 let pending;
5217 if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
5218 view.observer.delayAndroidKey(pending.key, pending.keyCode);
5219 if (pending.key == "Backspace" || pending.key == "Delete") {
5220 let startViewHeight = ((_b = window.visualViewport) === null || _b === void 0 ? void 0 : _b.height) || 0;
5221 setTimeout(() => {
5222 var _a;
5223 // Backspacing near uneditable nodes on Chrome Android sometimes
5224 // closes the virtual keyboard. This tries to crudely detect
5225 // that and refocus to get it back.
5226 if ((((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0) > startViewHeight + 10 && view.hasFocus) {
5227 view.contentDOM.blur();
5228 view.focus();
5229 }
5230 }, 100);
5231 }
5232 }
5233 if (browser.ios && event.inputType == "deleteContentForward") {
5234 // For some reason, DOM changes (and beforeinput) happen _before_
5235 // the key event for ctrl-d on iOS when using an external
5236 // keyboard.
5237 view.observer.flushSoon();
5238 }
5239 // Safari will occasionally forget to fire compositionend at the end of a dead-key composition
5240 if (browser.safari && event.inputType == "insertText" && view.inputState.composing >= 0) {
5241 setTimeout(() => observers.compositionend(view, event), 20);
5242 }
5243 return false;
5244};
5245const appliedFirefoxHack = new Set;
5246// In Firefox, when cut/copy handlers are added to the document, that
5247// somehow avoids a bug where those events aren't fired when the
5248// selection is empty. See https://github.com/codemirror/dev/issues/1082
5249// and https://bugzilla.mozilla.org/show_bug.cgi?id=995961
5250function firefoxCopyCutHack(doc) {
5251 if (!appliedFirefoxHack.has(doc)) {
5252 appliedFirefoxHack.add(doc);
5253 doc.addEventListener("copy", () => { });
5254 doc.addEventListener("cut", () => { });
5255 }
5256}
5257
5258const wrappingWhiteSpace = ["pre-wrap", "normal", "pre-line", "break-spaces"];
5259// Used to track, during updateHeight, if any actual heights changed
5260let heightChangeFlag = false;
5261function clearHeightChangeFlag() { heightChangeFlag = false; }
5262class HeightOracle {
5263 constructor(lineWrapping) {
5264 this.lineWrapping = lineWrapping;
5265 this.doc = state.Text.empty;
5266 this.heightSamples = {};
5267 this.lineHeight = 14; // The height of an entire line (line-height)
5268 this.charWidth = 7;
5269 this.textHeight = 14; // The height of the actual font (font-size)
5270 this.lineLength = 30;
5271 }
5272 heightForGap(from, to) {
5273 let lines = this.doc.lineAt(to).number - this.doc.lineAt(from).number + 1;
5274 if (this.lineWrapping)
5275 lines += Math.max(0, Math.ceil(((to - from) - (lines * this.lineLength * 0.5)) / this.lineLength));
5276 return this.lineHeight * lines;
5277 }
5278 heightForLine(length) {
5279 if (!this.lineWrapping)
5280 return this.lineHeight;
5281 let lines = 1 + Math.max(0, Math.ceil((length - this.lineLength) / Math.max(1, this.lineLength - 5)));
5282 return lines * this.lineHeight;
5283 }
5284 setDoc(doc) { this.doc = doc; return this; }
5285 mustRefreshForWrapping(whiteSpace) {
5286 return (wrappingWhiteSpace.indexOf(whiteSpace) > -1) != this.lineWrapping;
5287 }
5288 mustRefreshForHeights(lineHeights) {
5289 let newHeight = false;
5290 for (let i = 0; i < lineHeights.length; i++) {
5291 let h = lineHeights[i];
5292 if (h < 0) {
5293 i++;
5294 }
5295 else if (!this.heightSamples[Math.floor(h * 10)]) { // Round to .1 pixels
5296 newHeight = true;
5297 this.heightSamples[Math.floor(h * 10)] = true;
5298 }
5299 }
5300 return newHeight;
5301 }
5302 refresh(whiteSpace, lineHeight, charWidth, textHeight, lineLength, knownHeights) {
5303 let lineWrapping = wrappingWhiteSpace.indexOf(whiteSpace) > -1;
5304 let changed = Math.abs(lineHeight - this.lineHeight) > 0.3 || this.lineWrapping != lineWrapping ||
5305 Math.abs(charWidth - this.charWidth) > 0.1;
5306 this.lineWrapping = lineWrapping;
5307 this.lineHeight = lineHeight;
5308 this.charWidth = charWidth;
5309 this.textHeight = textHeight;
5310 this.lineLength = lineLength;
5311 if (changed) {
5312 this.heightSamples = {};
5313 for (let i = 0; i < knownHeights.length; i++) {
5314 let h = knownHeights[i];
5315 if (h < 0)
5316 i++;
5317 else
5318 this.heightSamples[Math.floor(h * 10)] = true;
5319 }
5320 }
5321 return changed;
5322 }
5323}
5324// This object is used by `updateHeight` to make DOM measurements
5325// arrive at the right nodes. The `heights` array is a sequence of
5326// block heights, starting from position `from`.
5327class MeasuredHeights {
5328 constructor(from, heights) {
5329 this.from = from;
5330 this.heights = heights;
5331 this.index = 0;
5332 }
5333 get more() { return this.index < this.heights.length; }
5334}
5335/**
5336Record used to represent information about a block-level element
5337in the editor view.
5338*/
5339class BlockInfo {
5340 /**
5341 @internal
5342 */
5343 constructor(
5344 /**
5345 The start of the element in the document.
5346 */
5347 from,
5348 /**
5349 The length of the element.
5350 */
5351 length,
5352 /**
5353 The top position of the element (relative to the top of the
5354 document).
5355 */
5356 top,
5357 /**
5358 Its height.
5359 */
5360 height,
5361 /**
5362 @internal Weird packed field that holds an array of children
5363 for composite blocks, a decoration for block widgets, and a
5364 number indicating the amount of widget-created line breaks for
5365 text blocks.
5366 */
5367 _content) {
5368 this.from = from;
5369 this.length = length;
5370 this.top = top;
5371 this.height = height;
5372 this._content = _content;
5373 }
5374 /**
5375 The type of element this is. When querying lines, this may be
5376 an array of all the blocks that make up the line.
5377 */
5378 get type() {
5379 return typeof this._content == "number" ? exports.BlockType.Text :
5380 Array.isArray(this._content) ? this._content : this._content.type;
5381 }
5382 /**
5383 The end of the element as a document position.
5384 */
5385 get to() { return this.from + this.length; }
5386 /**
5387 The bottom position of the element.
5388 */
5389 get bottom() { return this.top + this.height; }
5390 /**
5391 If this is a widget block, this will return the widget
5392 associated with it.
5393 */
5394 get widget() {
5395 return this._content instanceof PointDecoration ? this._content.widget : null;
5396 }
5397 /**
5398 If this is a textblock, this holds the number of line breaks
5399 that appear in widgets inside the block.
5400 */
5401 get widgetLineBreaks() {
5402 return typeof this._content == "number" ? this._content : 0;
5403 }
5404 /**
5405 @internal
5406 */
5407 join(other) {
5408 let content = (Array.isArray(this._content) ? this._content : [this])
5409 .concat(Array.isArray(other._content) ? other._content : [other]);
5410 return new BlockInfo(this.from, this.length + other.length, this.top, this.height + other.height, content);
5411 }
5412}
5413var QueryType;
5414(function (QueryType) {
5415 QueryType[QueryType["ByPos"] = 0] = "ByPos";
5416 QueryType[QueryType["ByHeight"] = 1] = "ByHeight";
5417 QueryType[QueryType["ByPosNoHeight"] = 2] = "ByPosNoHeight";
5418})(QueryType || (QueryType = {}));
5419const Epsilon = 1e-3;
5420class HeightMap {
5421 constructor(length, // The number of characters covered
5422 height, // Height of this part of the document
5423 flags = 2 /* Flag.Outdated */) {
5424 this.length = length;
5425 this.height = height;
5426 this.flags = flags;
5427 }
5428 get outdated() { return (this.flags & 2 /* Flag.Outdated */) > 0; }
5429 set outdated(value) { this.flags = (value ? 2 /* Flag.Outdated */ : 0) | (this.flags & ~2 /* Flag.Outdated */); }
5430 setHeight(height) {
5431 if (this.height != height) {
5432 if (Math.abs(this.height - height) > Epsilon)
5433 heightChangeFlag = true;
5434 this.height = height;
5435 }
5436 }
5437 // Base case is to replace a leaf node, which simply builds a tree
5438 // from the new nodes and returns that (HeightMapBranch and
5439 // HeightMapGap override this to actually use from/to)
5440 replace(_from, _to, nodes) {
5441 return HeightMap.of(nodes);
5442 }
5443 // Again, these are base cases, and are overridden for branch and gap nodes.
5444 decomposeLeft(_to, result) { result.push(this); }
5445 decomposeRight(_from, result) { result.push(this); }
5446 applyChanges(decorations, oldDoc, oracle, changes) {
5447 let me = this, doc = oracle.doc;
5448 for (let i = changes.length - 1; i >= 0; i--) {
5449 let { fromA, toA, fromB, toB } = changes[i];
5450 let start = me.lineAt(fromA, QueryType.ByPosNoHeight, oracle.setDoc(oldDoc), 0, 0);
5451 let end = start.to >= toA ? start : me.lineAt(toA, QueryType.ByPosNoHeight, oracle, 0, 0);
5452 toB += end.to - toA;
5453 toA = end.to;
5454 while (i > 0 && start.from <= changes[i - 1].toA) {
5455 fromA = changes[i - 1].fromA;
5456 fromB = changes[i - 1].fromB;
5457 i--;
5458 if (fromA < start.from)
5459 start = me.lineAt(fromA, QueryType.ByPosNoHeight, oracle, 0, 0);
5460 }
5461 fromB += start.from - fromA;
5462 fromA = start.from;
5463 let nodes = NodeBuilder.build(oracle.setDoc(doc), decorations, fromB, toB);
5464 me = replace(me, me.replace(fromA, toA, nodes));
5465 }
5466 return me.updateHeight(oracle, 0);
5467 }
5468 static empty() { return new HeightMapText(0, 0, 0); }
5469 // nodes uses null values to indicate the position of line breaks.
5470 // There are never line breaks at the start or end of the array, or
5471 // two line breaks next to each other, and the array isn't allowed
5472 // to be empty (same restrictions as return value from the builder).
5473 static of(nodes) {
5474 if (nodes.length == 1)
5475 return nodes[0];
5476 let i = 0, j = nodes.length, before = 0, after = 0;
5477 for (;;) {
5478 if (i == j) {
5479 if (before > after * 2) {
5480 let split = nodes[i - 1];
5481 if (split.break)
5482 nodes.splice(--i, 1, split.left, null, split.right);
5483 else
5484 nodes.splice(--i, 1, split.left, split.right);
5485 j += 1 + split.break;
5486 before -= split.size;
5487 }
5488 else if (after > before * 2) {
5489 let split = nodes[j];
5490 if (split.break)
5491 nodes.splice(j, 1, split.left, null, split.right);
5492 else
5493 nodes.splice(j, 1, split.left, split.right);
5494 j += 2 + split.break;
5495 after -= split.size;
5496 }
5497 else {
5498 break;
5499 }
5500 }
5501 else if (before < after) {
5502 let next = nodes[i++];
5503 if (next)
5504 before += next.size;
5505 }
5506 else {
5507 let next = nodes[--j];
5508 if (next)
5509 after += next.size;
5510 }
5511 }
5512 let brk = 0;
5513 if (nodes[i - 1] == null) {
5514 brk = 1;
5515 i--;
5516 }
5517 else if (nodes[i] == null) {
5518 brk = 1;
5519 j++;
5520 }
5521 return new HeightMapBranch(HeightMap.of(nodes.slice(0, i)), brk, HeightMap.of(nodes.slice(j)));
5522 }
5523}
5524function replace(old, val) {
5525 if (old == val)
5526 return old;
5527 if (old.constructor != val.constructor)
5528 heightChangeFlag = true;
5529 return val;
5530}
5531HeightMap.prototype.size = 1;
5532const SpaceDeco = Decoration.replace({});
5533class HeightMapBlock extends HeightMap {
5534 constructor(length, height, deco) {
5535 super(length, height);
5536 this.deco = deco;
5537 this.spaceAbove = 0;
5538 }
5539 mainBlock(top, offset) {
5540 return new BlockInfo(offset, this.length, top + this.spaceAbove, this.height - this.spaceAbove, this.deco || 0);
5541 }
5542 blockAt(height, _oracle, top, offset) {
5543 return this.spaceAbove && height < top + this.spaceAbove ? new BlockInfo(offset, 0, top, this.spaceAbove, SpaceDeco)
5544 : this.mainBlock(top, offset);
5545 }
5546 lineAt(_value, _type, oracle, top, offset) {
5547 let main = this.mainBlock(top, offset);
5548 return this.spaceAbove ? this.blockAt(0, oracle, top, offset).join(main) : main;
5549 }
5550 forEachLine(from, to, oracle, top, offset, f) {
5551 if (from <= offset + this.length && to >= offset)
5552 f(this.lineAt(0, QueryType.ByPos, oracle, top, offset));
5553 }
5554 setMeasuredHeight(measured) {
5555 let next = measured.heights[measured.index++];
5556 if (next < 0) {
5557 this.spaceAbove = -next;
5558 next = measured.heights[measured.index++];
5559 }
5560 else {
5561 this.spaceAbove = 0;
5562 }
5563 this.setHeight(next);
5564 }
5565 updateHeight(oracle, offset = 0, _force = false, measured) {
5566 if (measured && measured.from <= offset && measured.more)
5567 this.setMeasuredHeight(measured);
5568 this.outdated = false;
5569 return this;
5570 }
5571 toString() { return `block(${this.length})`; }
5572}
5573class HeightMapText extends HeightMapBlock {
5574 constructor(length, height, above) {
5575 super(length, height, null);
5576 this.collapsed = 0; // Amount of collapsed content in the line
5577 this.widgetHeight = 0; // Maximum inline widget height
5578 this.breaks = 0; // Number of widget-introduced line breaks on the line
5579 this.spaceAbove = above;
5580 }
5581 mainBlock(top, offset) {
5582 return new BlockInfo(offset, this.length, top + this.spaceAbove, this.height - this.spaceAbove, this.breaks);
5583 }
5584 replace(_from, _to, nodes) {
5585 let node = nodes[0];
5586 if (nodes.length == 1 && (node instanceof HeightMapText || node instanceof HeightMapGap && (node.flags & 4 /* Flag.SingleLine */)) &&
5587 Math.abs(this.length - node.length) < 10) {
5588 if (node instanceof HeightMapGap)
5589 node = new HeightMapText(node.length, this.height, this.spaceAbove);
5590 else
5591 node.height = this.height;
5592 if (!this.outdated)
5593 node.outdated = false;
5594 return node;
5595 }
5596 else {
5597 return HeightMap.of(nodes);
5598 }
5599 }
5600 updateHeight(oracle, offset = 0, force = false, measured) {
5601 if (measured && measured.from <= offset && measured.more) {
5602 this.setMeasuredHeight(measured);
5603 }
5604 else if (force || this.outdated) {
5605 this.spaceAbove = 0;
5606 this.setHeight(Math.max(this.widgetHeight, oracle.heightForLine(this.length - this.collapsed)) +
5607 this.breaks * oracle.lineHeight);
5608 }
5609 this.outdated = false;
5610 return this;
5611 }
5612 toString() {
5613 return `line(${this.length}${this.collapsed ? -this.collapsed : ""}${this.widgetHeight ? ":" + this.widgetHeight : ""})`;
5614 }
5615}
5616class HeightMapGap extends HeightMap {
5617 constructor(length) { super(length, 0); }
5618 heightMetrics(oracle, offset) {
5619 let firstLine = oracle.doc.lineAt(offset).number, lastLine = oracle.doc.lineAt(offset + this.length).number;
5620 let lines = lastLine - firstLine + 1;
5621 let perLine, perChar = 0;
5622 if (oracle.lineWrapping) {
5623 let totalPerLine = Math.min(this.height, oracle.lineHeight * lines);
5624 perLine = totalPerLine / lines;
5625 if (this.length > lines + 1)
5626 perChar = (this.height - totalPerLine) / (this.length - lines - 1);
5627 }
5628 else {
5629 perLine = this.height / lines;
5630 }
5631 return { firstLine, lastLine, perLine, perChar };
5632 }
5633 blockAt(height, oracle, top, offset) {
5634 let { firstLine, lastLine, perLine, perChar } = this.heightMetrics(oracle, offset);
5635 if (oracle.lineWrapping) {
5636 let guess = offset + (height < oracle.lineHeight ? 0
5637 : Math.round(Math.max(0, Math.min(1, (height - top) / this.height)) * this.length));
5638 let line = oracle.doc.lineAt(guess), lineHeight = perLine + line.length * perChar;
5639 let lineTop = Math.max(top, height - lineHeight / 2);
5640 return new BlockInfo(line.from, line.length, lineTop, lineHeight, 0);
5641 }
5642 else {
5643 let line = Math.max(0, Math.min(lastLine - firstLine, Math.floor((height - top) / perLine)));
5644 let { from, length } = oracle.doc.line(firstLine + line);
5645 return new BlockInfo(from, length, top + perLine * line, perLine, 0);
5646 }
5647 }
5648 lineAt(value, type, oracle, top, offset) {
5649 if (type == QueryType.ByHeight)
5650 return this.blockAt(value, oracle, top, offset);
5651 if (type == QueryType.ByPosNoHeight) {
5652 let { from, to } = oracle.doc.lineAt(value);
5653 return new BlockInfo(from, to - from, 0, 0, 0);
5654 }
5655 let { firstLine, perLine, perChar } = this.heightMetrics(oracle, offset);
5656 let line = oracle.doc.lineAt(value), lineHeight = perLine + line.length * perChar;
5657 let linesAbove = line.number - firstLine;
5658 let lineTop = top + perLine * linesAbove + perChar * (line.from - offset - linesAbove);
5659 return new BlockInfo(line.from, line.length, Math.max(top, Math.min(lineTop, top + this.height - lineHeight)), lineHeight, 0);
5660 }
5661 forEachLine(from, to, oracle, top, offset, f) {
5662 from = Math.max(from, offset);
5663 to = Math.min(to, offset + this.length);
5664 let { firstLine, perLine, perChar } = this.heightMetrics(oracle, offset);
5665 for (let pos = from, lineTop = top; pos <= to;) {
5666 let line = oracle.doc.lineAt(pos);
5667 if (pos == from) {
5668 let linesAbove = line.number - firstLine;
5669 lineTop += perLine * linesAbove + perChar * (from - offset - linesAbove);
5670 }
5671 let lineHeight = perLine + perChar * line.length;
5672 f(new BlockInfo(line.from, line.length, lineTop, lineHeight, 0));
5673 lineTop += lineHeight;
5674 pos = line.to + 1;
5675 }
5676 }
5677 replace(from, to, nodes) {
5678 let after = this.length - to;
5679 if (after > 0) {
5680 let last = nodes[nodes.length - 1];
5681 if (last instanceof HeightMapGap)
5682 nodes[nodes.length - 1] = new HeightMapGap(last.length + after);
5683 else
5684 nodes.push(null, new HeightMapGap(after - 1));
5685 }
5686 if (from > 0) {
5687 let first = nodes[0];
5688 if (first instanceof HeightMapGap)
5689 nodes[0] = new HeightMapGap(from + first.length);
5690 else
5691 nodes.unshift(new HeightMapGap(from - 1), null);
5692 }
5693 return HeightMap.of(nodes);
5694 }
5695 decomposeLeft(to, result) {
5696 result.push(new HeightMapGap(to - 1), null);
5697 }
5698 decomposeRight(from, result) {
5699 result.push(null, new HeightMapGap(this.length - from - 1));
5700 }
5701 updateHeight(oracle, offset = 0, force = false, measured) {
5702 let end = offset + this.length;
5703 if (measured && measured.from <= offset + this.length && measured.more) {
5704 // Fill in part of this gap with measured lines. We know there
5705 // can't be widgets or collapsed ranges in those lines, because
5706 // they would already have been added to the heightmap (gaps
5707 // only contain plain text).
5708 let nodes = [], pos = Math.max(offset, measured.from), singleHeight = -1;
5709 if (measured.from > offset)
5710 nodes.push(new HeightMapGap(measured.from - offset - 1).updateHeight(oracle, offset));
5711 while (pos <= end && measured.more) {
5712 let len = oracle.doc.lineAt(pos).length;
5713 if (nodes.length)
5714 nodes.push(null);
5715 let height = measured.heights[measured.index++], above = 0;
5716 if (height < 0) {
5717 above = -height;
5718 height = measured.heights[measured.index++];
5719 }
5720 if (singleHeight == -1)
5721 singleHeight = height;
5722 else if (Math.abs(height - singleHeight) >= Epsilon)
5723 singleHeight = -2;
5724 let line = new HeightMapText(len, height, above);
5725 line.outdated = false;
5726 nodes.push(line);
5727 pos += len + 1;
5728 }
5729 if (pos <= end)
5730 nodes.push(null, new HeightMapGap(end - pos).updateHeight(oracle, pos));
5731 let result = HeightMap.of(nodes);
5732 if (singleHeight < 0 || Math.abs(result.height - this.height) >= Epsilon ||
5733 Math.abs(singleHeight - this.heightMetrics(oracle, offset).perLine) >= Epsilon)
5734 heightChangeFlag = true;
5735 return replace(this, result);
5736 }
5737 else if (force || this.outdated) {
5738 this.setHeight(oracle.heightForGap(offset, offset + this.length));
5739 this.outdated = false;
5740 }
5741 return this;
5742 }
5743 toString() { return `gap(${this.length})`; }
5744}
5745class HeightMapBranch extends HeightMap {
5746 constructor(left, brk, right) {
5747 super(left.length + brk + right.length, left.height + right.height, brk | (left.outdated || right.outdated ? 2 /* Flag.Outdated */ : 0));
5748 this.left = left;
5749 this.right = right;
5750 this.size = left.size + right.size;
5751 }
5752 get break() { return this.flags & 1 /* Flag.Break */; }
5753 blockAt(height, oracle, top, offset) {
5754 let mid = top + this.left.height;
5755 return height < mid ? this.left.blockAt(height, oracle, top, offset)
5756 : this.right.blockAt(height, oracle, mid, offset + this.left.length + this.break);
5757 }
5758 lineAt(value, type, oracle, top, offset) {
5759 let rightTop = top + this.left.height, rightOffset = offset + this.left.length + this.break;
5760 let left = type == QueryType.ByHeight ? value < rightTop : value < rightOffset;
5761 let base = left ? this.left.lineAt(value, type, oracle, top, offset)
5762 : this.right.lineAt(value, type, oracle, rightTop, rightOffset);
5763 if (this.break || (left ? base.to < rightOffset : base.from > rightOffset))
5764 return base;
5765 let subQuery = type == QueryType.ByPosNoHeight ? QueryType.ByPosNoHeight : QueryType.ByPos;
5766 if (left)
5767 return base.join(this.right.lineAt(rightOffset, subQuery, oracle, rightTop, rightOffset));
5768 else
5769 return this.left.lineAt(rightOffset, subQuery, oracle, top, offset).join(base);
5770 }
5771 forEachLine(from, to, oracle, top, offset, f) {
5772 let rightTop = top + this.left.height, rightOffset = offset + this.left.length + this.break;
5773 if (this.break) {
5774 if (from < rightOffset)
5775 this.left.forEachLine(from, to, oracle, top, offset, f);
5776 if (to >= rightOffset)
5777 this.right.forEachLine(from, to, oracle, rightTop, rightOffset, f);
5778 }
5779 else {
5780 let mid = this.lineAt(rightOffset, QueryType.ByPos, oracle, top, offset);
5781 if (from < mid.from)
5782 this.left.forEachLine(from, mid.from - 1, oracle, top, offset, f);
5783 if (mid.to >= from && mid.from <= to)
5784 f(mid);
5785 if (to > mid.to)
5786 this.right.forEachLine(mid.to + 1, to, oracle, rightTop, rightOffset, f);
5787 }
5788 }
5789 replace(from, to, nodes) {
5790 let rightStart = this.left.length + this.break;
5791 if (to < rightStart)
5792 return this.balanced(this.left.replace(from, to, nodes), this.right);
5793 if (from > this.left.length)
5794 return this.balanced(this.left, this.right.replace(from - rightStart, to - rightStart, nodes));
5795 let result = [];
5796 if (from > 0)
5797 this.decomposeLeft(from, result);
5798 let left = result.length;
5799 for (let node of nodes)
5800 result.push(node);
5801 if (from > 0)
5802 mergeGaps(result, left - 1);
5803 if (to < this.length) {
5804 let right = result.length;
5805 this.decomposeRight(to, result);
5806 mergeGaps(result, right);
5807 }
5808 return HeightMap.of(result);
5809 }
5810 decomposeLeft(to, result) {
5811 let left = this.left.length;
5812 if (to <= left)
5813 return this.left.decomposeLeft(to, result);
5814 result.push(this.left);
5815 if (this.break) {
5816 left++;
5817 if (to >= left)
5818 result.push(null);
5819 }
5820 if (to > left)
5821 this.right.decomposeLeft(to - left, result);
5822 }
5823 decomposeRight(from, result) {
5824 let left = this.left.length, right = left + this.break;
5825 if (from >= right)
5826 return this.right.decomposeRight(from - right, result);
5827 if (from < left)
5828 this.left.decomposeRight(from, result);
5829 if (this.break && from < right)
5830 result.push(null);
5831 result.push(this.right);
5832 }
5833 balanced(left, right) {
5834 if (left.size > 2 * right.size || right.size > 2 * left.size)
5835 return HeightMap.of(this.break ? [left, null, right] : [left, right]);
5836 this.left = replace(this.left, left);
5837 this.right = replace(this.right, right);
5838 this.setHeight(left.height + right.height);
5839 this.outdated = left.outdated || right.outdated;
5840 this.size = left.size + right.size;
5841 this.length = left.length + this.break + right.length;
5842 return this;
5843 }
5844 updateHeight(oracle, offset = 0, force = false, measured) {
5845 let { left, right } = this, rightStart = offset + left.length + this.break, rebalance = null;
5846 if (measured && measured.from <= offset + left.length && measured.more)
5847 rebalance = left = left.updateHeight(oracle, offset, force, measured);
5848 else
5849 left.updateHeight(oracle, offset, force);
5850 if (measured && measured.from <= rightStart + right.length && measured.more)
5851 rebalance = right = right.updateHeight(oracle, rightStart, force, measured);
5852 else
5853 right.updateHeight(oracle, rightStart, force);
5854 if (rebalance)
5855 return this.balanced(left, right);
5856 this.height = this.left.height + this.right.height;
5857 this.outdated = false;
5858 return this;
5859 }
5860 toString() { return this.left + (this.break ? " " : "-") + this.right; }
5861}
5862function mergeGaps(nodes, around) {
5863 let before, after;
5864 if (nodes[around] == null &&
5865 (before = nodes[around - 1]) instanceof HeightMapGap &&
5866 (after = nodes[around + 1]) instanceof HeightMapGap)
5867 nodes.splice(around - 1, 3, new HeightMapGap(before.length + 1 + after.length));
5868}
5869const relevantWidgetHeight = 5;
5870class NodeBuilder {
5871 constructor(pos, oracle) {
5872 this.pos = pos;
5873 this.oracle = oracle;
5874 this.nodes = [];
5875 this.lineStart = -1;
5876 this.lineEnd = -1;
5877 this.covering = null;
5878 this.writtenTo = pos;
5879 }
5880 get isCovered() {
5881 return this.covering && this.nodes[this.nodes.length - 1] == this.covering;
5882 }
5883 span(_from, to) {
5884 if (this.lineStart > -1) {
5885 let end = Math.min(to, this.lineEnd), last = this.nodes[this.nodes.length - 1];
5886 if (last instanceof HeightMapText)
5887 last.length += end - this.pos;
5888 else if (end > this.pos || !this.isCovered)
5889 this.nodes.push(new HeightMapText(end - this.pos, -1, 0));
5890 this.writtenTo = end;
5891 if (to > end) {
5892 this.nodes.push(null);
5893 this.writtenTo++;
5894 this.lineStart = -1;
5895 }
5896 }
5897 this.pos = to;
5898 }
5899 point(from, to, deco) {
5900 if (from < to || deco.heightRelevant) {
5901 let height = deco.widget ? deco.widget.estimatedHeight : 0;
5902 let breaks = deco.widget ? deco.widget.lineBreaks : 0;
5903 if (height < 0)
5904 height = this.oracle.lineHeight;
5905 let len = to - from;
5906 if (deco.block) {
5907 this.addBlock(new HeightMapBlock(len, height, deco));
5908 }
5909 else if (len || breaks || height >= relevantWidgetHeight) {
5910 this.addLineDeco(height, breaks, len);
5911 }
5912 }
5913 else if (to > from) {
5914 this.span(from, to);
5915 }
5916 if (this.lineEnd > -1 && this.lineEnd < this.pos)
5917 this.lineEnd = this.oracle.doc.lineAt(this.pos).to;
5918 }
5919 enterLine() {
5920 if (this.lineStart > -1)
5921 return;
5922 let { from, to } = this.oracle.doc.lineAt(this.pos);
5923 this.lineStart = from;
5924 this.lineEnd = to;
5925 if (this.writtenTo < from) {
5926 if (this.writtenTo < from - 1 || this.nodes[this.nodes.length - 1] == null)
5927 this.nodes.push(this.blankContent(this.writtenTo, from - 1));
5928 this.nodes.push(null);
5929 }
5930 if (this.pos > from)
5931 this.nodes.push(new HeightMapText(this.pos - from, -1, 0));
5932 this.writtenTo = this.pos;
5933 }
5934 blankContent(from, to) {
5935 let gap = new HeightMapGap(to - from);
5936 if (this.oracle.doc.lineAt(from).to == to)
5937 gap.flags |= 4 /* Flag.SingleLine */;
5938 return gap;
5939 }
5940 ensureLine() {
5941 this.enterLine();
5942 let last = this.nodes.length ? this.nodes[this.nodes.length - 1] : null;
5943 if (last instanceof HeightMapText)
5944 return last;
5945 let line = new HeightMapText(0, -1, 0);
5946 this.nodes.push(line);
5947 return line;
5948 }
5949 addBlock(block) {
5950 this.enterLine();
5951 let deco = block.deco;
5952 if (deco && deco.startSide > 0 && !this.isCovered)
5953 this.ensureLine();
5954 this.nodes.push(block);
5955 this.writtenTo = this.pos = this.pos + block.length;
5956 if (deco && deco.endSide > 0)
5957 this.covering = block;
5958 }
5959 addLineDeco(height, breaks, length) {
5960 let line = this.ensureLine();
5961 line.length += length;
5962 line.collapsed += length;
5963 line.widgetHeight = Math.max(line.widgetHeight, height);
5964 line.breaks += breaks;
5965 this.writtenTo = this.pos = this.pos + length;
5966 }
5967 finish(from) {
5968 let last = this.nodes.length == 0 ? null : this.nodes[this.nodes.length - 1];
5969 if (this.lineStart > -1 && !(last instanceof HeightMapText) && !this.isCovered)
5970 this.nodes.push(new HeightMapText(0, -1, 0));
5971 else if (this.writtenTo < this.pos || last == null)
5972 this.nodes.push(this.blankContent(this.writtenTo, this.pos));
5973 let pos = from;
5974 for (let node of this.nodes) {
5975 if (node instanceof HeightMapText)
5976 node.updateHeight(this.oracle, pos);
5977 pos += node ? node.length : 1;
5978 }
5979 return this.nodes;
5980 }
5981 // Always called with a region that on both sides either stretches
5982 // to a line break or the end of the document.
5983 // The returned array uses null to indicate line breaks, but never
5984 // starts or ends in a line break, or has multiple line breaks next
5985 // to each other.
5986 static build(oracle, decorations, from, to) {
5987 let builder = new NodeBuilder(from, oracle);
5988 state.RangeSet.spans(decorations, from, to, builder, 0);
5989 return builder.finish(from);
5990 }
5991}
5992function heightRelevantDecoChanges(a, b, diff) {
5993 let comp = new DecorationComparator;
5994 state.RangeSet.compare(a, b, diff, comp, 0);
5995 return comp.changes;
5996}
5997class DecorationComparator {
5998 constructor() {
5999 this.changes = [];
6000 }
6001 compareRange() { }
6002 comparePoint(from, to, a, b) {
6003 if (from < to || a && a.heightRelevant || b && b.heightRelevant)
6004 addRange(from, to, this.changes, 5);
6005 }
6006}
6007
6008function visiblePixelRange(dom, paddingTop) {
6009 let rect = dom.getBoundingClientRect();
6010 let doc = dom.ownerDocument, win = doc.defaultView || window;
6011 let left = Math.max(0, rect.left), right = Math.min(win.innerWidth, rect.right);
6012 let top = Math.max(0, rect.top), bottom = Math.min(win.innerHeight, rect.bottom);
6013 for (let parent = dom.parentNode; parent && parent != doc.body;) {
6014 if (parent.nodeType == 1) {
6015 let elt = parent;
6016 let style = window.getComputedStyle(elt);
6017 if ((elt.scrollHeight > elt.clientHeight || elt.scrollWidth > elt.clientWidth) &&
6018 style.overflow != "visible") {
6019 let parentRect = elt.getBoundingClientRect();
6020 left = Math.max(left, parentRect.left);
6021 right = Math.min(right, parentRect.right);
6022 top = Math.max(top, parentRect.top);
6023 bottom = Math.min(parent == dom.parentNode ? win.innerHeight : bottom, parentRect.bottom);
6024 }
6025 parent = style.position == "absolute" || style.position == "fixed" ? elt.offsetParent : elt.parentNode;
6026 }
6027 else if (parent.nodeType == 11) { // Shadow root
6028 parent = parent.host;
6029 }
6030 else {
6031 break;
6032 }
6033 }
6034 return { left: left - rect.left, right: Math.max(left, right) - rect.left,
6035 top: top - (rect.top + paddingTop), bottom: Math.max(top, bottom) - (rect.top + paddingTop) };
6036}
6037function inWindow(elt) {
6038 let rect = elt.getBoundingClientRect(), win = elt.ownerDocument.defaultView || window;
6039 return rect.left < win.innerWidth && rect.right > 0 &&
6040 rect.top < win.innerHeight && rect.bottom > 0;
6041}
6042function fullPixelRange(dom, paddingTop) {
6043 let rect = dom.getBoundingClientRect();
6044 return { left: 0, right: rect.right - rect.left,
6045 top: paddingTop, bottom: rect.bottom - (rect.top + paddingTop) };
6046}
6047// Line gaps are placeholder widgets used to hide pieces of overlong
6048// lines within the viewport, as a kludge to keep the editor
6049// responsive when a ridiculously long line is loaded into it.
6050class LineGap {
6051 constructor(from, to, size, displaySize) {
6052 this.from = from;
6053 this.to = to;
6054 this.size = size;
6055 this.displaySize = displaySize;
6056 }
6057 static same(a, b) {
6058 if (a.length != b.length)
6059 return false;
6060 for (let i = 0; i < a.length; i++) {
6061 let gA = a[i], gB = b[i];
6062 if (gA.from != gB.from || gA.to != gB.to || gA.size != gB.size)
6063 return false;
6064 }
6065 return true;
6066 }
6067 draw(viewState, wrapping) {
6068 return Decoration.replace({
6069 widget: new LineGapWidget(this.displaySize * (wrapping ? viewState.scaleY : viewState.scaleX), wrapping)
6070 }).range(this.from, this.to);
6071 }
6072}
6073class LineGapWidget extends WidgetType {
6074 constructor(size, vertical) {
6075 super();
6076 this.size = size;
6077 this.vertical = vertical;
6078 }
6079 eq(other) { return other.size == this.size && other.vertical == this.vertical; }
6080 toDOM() {
6081 let elt = document.createElement("div");
6082 if (this.vertical) {
6083 elt.style.height = this.size + "px";
6084 }
6085 else {
6086 elt.style.width = this.size + "px";
6087 elt.style.height = "2px";
6088 elt.style.display = "inline-block";
6089 }
6090 return elt;
6091 }
6092 get estimatedHeight() { return this.vertical ? this.size : -1; }
6093}
6094class ViewState {
6095 constructor(view, state$1) {
6096 this.view = view;
6097 this.state = state$1;
6098 // These are contentDOM-local coordinates
6099 this.pixelViewport = { left: 0, right: window.innerWidth, top: 0, bottom: 0 };
6100 this.inView = true;
6101 this.paddingTop = 0; // Padding above the document, scaled
6102 this.paddingBottom = 0; // Padding below the document, scaled
6103 this.contentDOMWidth = 0; // contentDOM.getBoundingClientRect().width
6104 this.contentDOMHeight = 0; // contentDOM.getBoundingClientRect().height
6105 this.editorHeight = 0; // scrollDOM.clientHeight, unscaled
6106 this.editorWidth = 0; // scrollDOM.clientWidth, unscaled
6107 // The CSS-transformation scale of the editor (transformed size /
6108 // concrete size)
6109 this.scaleX = 1;
6110 this.scaleY = 1;
6111 // Last seen vertical offset of the element at the top of the scroll
6112 // container, or top of the window if there's no wrapping scroller
6113 this.scrollOffset = 0;
6114 this.scrolledToBottom = false;
6115 // The vertical position (document-relative) to which to anchor the
6116 // scroll position. -1 means anchor to the end of the document.
6117 this.scrollAnchorPos = 0;
6118 // The height at the anchor position. Set by the DOM update phase.
6119 // -1 means no height available.
6120 this.scrollAnchorHeight = -1;
6121 // See VP.MaxDOMHeight
6122 this.scaler = IdScaler;
6123 this.scrollTarget = null;
6124 // Briefly set to true when printing, to disable viewport limiting
6125 this.printing = false;
6126 // Flag set when editor content was redrawn, so that the next
6127 // measure stage knows it must read DOM layout
6128 this.mustMeasureContent = true;
6129 this.defaultTextDirection = exports.Direction.LTR;
6130 this.visibleRanges = [];
6131 // Cursor 'assoc' is only significant when the cursor is on a line
6132 // wrap point, where it must stick to the character that it is
6133 // associated with. Since browsers don't provide a reasonable
6134 // interface to set or query this, when a selection is set that
6135 // might cause this to be significant, this flag is set. The next
6136 // measure phase will check whether the cursor is on a line-wrapping
6137 // boundary and, if so, reset it to make sure it is positioned in
6138 // the right place.
6139 this.mustEnforceCursorAssoc = false;
6140 let guessWrapping = state$1.facet(contentAttributes).some(v => typeof v != "function" && v.class == "cm-lineWrapping");
6141 this.heightOracle = new HeightOracle(guessWrapping);
6142 this.stateDeco = staticDeco(state$1);
6143 this.heightMap = HeightMap.empty().applyChanges(this.stateDeco, state.Text.empty, this.heightOracle.setDoc(state$1.doc), [new ChangedRange(0, 0, 0, state$1.doc.length)]);
6144 for (let i = 0; i < 2; i++) {
6145 this.viewport = this.getViewport(0, null);
6146 if (!this.updateForViewport())
6147 break;
6148 }
6149 this.updateViewportLines();
6150 this.lineGaps = this.ensureLineGaps([]);
6151 this.lineGapDeco = Decoration.set(this.lineGaps.map(gap => gap.draw(this, false)));
6152 this.scrollParent = view.scrollDOM;
6153 this.computeVisibleRanges();
6154 }
6155 updateForViewport() {
6156 let viewports = [this.viewport], { main } = this.state.selection;
6157 for (let i = 0; i <= 1; i++) {
6158 let pos = i ? main.head : main.anchor;
6159 if (!viewports.some(({ from, to }) => pos >= from && pos <= to)) {
6160 let { from, to } = this.lineBlockAt(pos);
6161 viewports.push(new Viewport(from, to));
6162 }
6163 }
6164 this.viewports = viewports.sort((a, b) => a.from - b.from);
6165 return this.updateScaler();
6166 }
6167 updateScaler() {
6168 let scaler = this.scaler;
6169 this.scaler = this.heightMap.height <= 7000000 /* VP.MaxDOMHeight */ ? IdScaler :
6170 new BigScaler(this.heightOracle, this.heightMap, this.viewports);
6171 return scaler.eq(this.scaler) ? 0 : 2 /* UpdateFlag.Height */;
6172 }
6173 updateViewportLines() {
6174 this.viewportLines = [];
6175 this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.heightOracle.setDoc(this.state.doc), 0, 0, block => {
6176 this.viewportLines.push(scaleBlock(block, this.scaler));
6177 });
6178 }
6179 update(update, scrollTarget = null) {
6180 this.state = update.state;
6181 let prevDeco = this.stateDeco;
6182 this.stateDeco = staticDeco(this.state);
6183 let contentChanges = update.changedRanges;
6184 let heightChanges = ChangedRange.extendWithRanges(contentChanges, heightRelevantDecoChanges(prevDeco, this.stateDeco, update ? update.changes : state.ChangeSet.empty(this.state.doc.length)));
6185 let prevHeight = this.heightMap.height;
6186 let scrollAnchor = this.scrolledToBottom ? null : this.scrollAnchorAt(this.scrollOffset);
6187 clearHeightChangeFlag();
6188 this.heightMap = this.heightMap.applyChanges(this.stateDeco, update.startState.doc, this.heightOracle.setDoc(this.state.doc), heightChanges);
6189 if (this.heightMap.height != prevHeight || heightChangeFlag)
6190 update.flags |= 2 /* UpdateFlag.Height */;
6191 if (scrollAnchor) {
6192 this.scrollAnchorPos = update.changes.mapPos(scrollAnchor.from, -1);
6193 this.scrollAnchorHeight = scrollAnchor.top;
6194 }
6195 else {
6196 this.scrollAnchorPos = -1;
6197 this.scrollAnchorHeight = prevHeight;
6198 }
6199 let viewport = heightChanges.length ? this.mapViewport(this.viewport, update.changes) : this.viewport;
6200 if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
6201 !this.viewportIsAppropriate(viewport))
6202 viewport = this.getViewport(0, scrollTarget);
6203 let viewportChange = viewport.from != this.viewport.from || viewport.to != this.viewport.to;
6204 this.viewport = viewport;
6205 update.flags |= this.updateForViewport();
6206 if (viewportChange || !update.changes.empty || (update.flags & 2 /* UpdateFlag.Height */))
6207 this.updateViewportLines();
6208 if (this.lineGaps.length || this.viewport.to - this.viewport.from > (2000 /* LG.Margin */ << 1))
6209 this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
6210 update.flags |= this.computeVisibleRanges(update.changes);
6211 if (scrollTarget)
6212 this.scrollTarget = scrollTarget;
6213 if (!this.mustEnforceCursorAssoc && (update.selectionSet || update.focusChanged) && update.view.lineWrapping &&
6214 update.state.selection.main.empty && update.state.selection.main.assoc &&
6215 !update.state.facet(nativeSelectionHidden))
6216 this.mustEnforceCursorAssoc = true;
6217 }
6218 measure() {
6219 let { view } = this, dom = view.contentDOM, style = window.getComputedStyle(dom);
6220 let oracle = this.heightOracle;
6221 let whiteSpace = style.whiteSpace;
6222 this.defaultTextDirection = style.direction == "rtl" ? exports.Direction.RTL : exports.Direction.LTR;
6223 let refresh = this.heightOracle.mustRefreshForWrapping(whiteSpace) || this.mustMeasureContent === "refresh";
6224 let domRect = dom.getBoundingClientRect();
6225 let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != domRect.height;
6226 this.contentDOMHeight = domRect.height;
6227 this.mustMeasureContent = false;
6228 let result = 0, bias = 0;
6229 if (domRect.width && domRect.height) {
6230 let { scaleX, scaleY } = getScale(dom, domRect);
6231 if (scaleX > .005 && Math.abs(this.scaleX - scaleX) > .005 ||
6232 scaleY > .005 && Math.abs(this.scaleY - scaleY) > .005) {
6233 this.scaleX = scaleX;
6234 this.scaleY = scaleY;
6235 result |= 16 /* UpdateFlag.Geometry */;
6236 refresh = measureContent = true;
6237 }
6238 }
6239 // Vertical padding
6240 let paddingTop = (parseInt(style.paddingTop) || 0) * this.scaleY;
6241 let paddingBottom = (parseInt(style.paddingBottom) || 0) * this.scaleY;
6242 if (this.paddingTop != paddingTop || this.paddingBottom != paddingBottom) {
6243 this.paddingTop = paddingTop;
6244 this.paddingBottom = paddingBottom;
6245 result |= 16 /* UpdateFlag.Geometry */ | 2 /* UpdateFlag.Height */;
6246 }
6247 if (this.editorWidth != view.scrollDOM.clientWidth) {
6248 if (oracle.lineWrapping)
6249 measureContent = true;
6250 this.editorWidth = view.scrollDOM.clientWidth;
6251 result |= 16 /* UpdateFlag.Geometry */;
6252 }
6253 let scrollParent = scrollableParents(this.view.contentDOM, false).y;
6254 if (scrollParent != this.scrollParent) {
6255 this.scrollParent = scrollParent;
6256 this.scrollAnchorHeight = -1;
6257 this.scrollOffset = 0;
6258 }
6259 let scrollOffset = this.getScrollOffset();
6260 if (this.scrollOffset != scrollOffset) {
6261 this.scrollAnchorHeight = -1;
6262 this.scrollOffset = scrollOffset;
6263 }
6264 this.scrolledToBottom = isScrolledToBottom(this.scrollParent || view.win);
6265 // Pixel viewport
6266 let pixelViewport = (this.printing ? fullPixelRange : visiblePixelRange)(dom, this.paddingTop);
6267 let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
6268 this.pixelViewport = pixelViewport;
6269 let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
6270 if (inView != this.inView) {
6271 this.inView = inView;
6272 if (inView)
6273 measureContent = true;
6274 }
6275 if (!this.inView && !this.scrollTarget && !inWindow(view.dom))
6276 return 0;
6277 let contentWidth = domRect.width;
6278 if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight) {
6279 this.contentDOMWidth = domRect.width;
6280 this.editorHeight = view.scrollDOM.clientHeight;
6281 result |= 16 /* UpdateFlag.Geometry */;
6282 }
6283 if (measureContent) {
6284 let lineHeights = view.docView.measureVisibleLineHeights(this.viewport);
6285 if (oracle.mustRefreshForHeights(lineHeights))
6286 refresh = true;
6287 if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
6288 let { lineHeight, charWidth, textHeight } = view.docView.measureTextSize();
6289 refresh = lineHeight > 0 && oracle.refresh(whiteSpace, lineHeight, charWidth, textHeight, Math.max(5, contentWidth / charWidth), lineHeights);
6290 if (refresh) {
6291 view.docView.minWidth = 0;
6292 result |= 16 /* UpdateFlag.Geometry */;
6293 }
6294 }
6295 if (dTop > 0 && dBottom > 0)
6296 bias = Math.max(dTop, dBottom);
6297 else if (dTop < 0 && dBottom < 0)
6298 bias = Math.min(dTop, dBottom);
6299 clearHeightChangeFlag();
6300 for (let vp of this.viewports) {
6301 let heights = vp.from == this.viewport.from ? lineHeights : view.docView.measureVisibleLineHeights(vp);
6302 this.heightMap = (refresh ? HeightMap.empty().applyChanges(this.stateDeco, state.Text.empty, this.heightOracle, [new ChangedRange(0, 0, 0, view.state.doc.length)]) : this.heightMap).updateHeight(oracle, 0, refresh, new MeasuredHeights(vp.from, heights));
6303 }
6304 if (heightChangeFlag)
6305 result |= 2 /* UpdateFlag.Height */;
6306 }
6307 let viewportChange = !this.viewportIsAppropriate(this.viewport, bias) ||
6308 this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from ||
6309 this.scrollTarget.range.head > this.viewport.to);
6310 if (viewportChange) {
6311 if (result & 2 /* UpdateFlag.Height */)
6312 result |= this.updateScaler();
6313 this.viewport = this.getViewport(bias, this.scrollTarget);
6314 result |= this.updateForViewport();
6315 }
6316 if ((result & 2 /* UpdateFlag.Height */) || viewportChange)
6317 this.updateViewportLines();
6318 if (this.lineGaps.length || this.viewport.to - this.viewport.from > (2000 /* LG.Margin */ << 1))
6319 this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps, view));
6320 result |= this.computeVisibleRanges();
6321 if (this.mustEnforceCursorAssoc) {
6322 this.mustEnforceCursorAssoc = false;
6323 // This is done in the read stage, because moving the selection
6324 // to a line end is going to trigger a layout anyway, so it
6325 // can't be a pure write. It should be rare that it does any
6326 // writing.
6327 view.docView.enforceCursorAssoc();
6328 }
6329 return result;
6330 }
6331 get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top); }
6332 get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom); }
6333 getViewport(bias, scrollTarget) {
6334 // This will divide VP.Margin between the top and the
6335 // bottom, depending on the bias (the change in viewport position
6336 // since the last update). It'll hold a number between 0 and 1
6337 let marginTop = 0.5 - Math.max(-0.5, Math.min(0.5, bias / 1000 /* VP.Margin */ / 2));
6338 let map = this.heightMap, oracle = this.heightOracle;
6339 let { visibleTop, visibleBottom } = this;
6340 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);
6341 // If scrollTarget is given, make sure the viewport includes that position
6342 if (scrollTarget) {
6343 let { head } = scrollTarget.range;
6344 if (head < viewport.from || head > viewport.to) {
6345 let viewHeight = Math.min(this.editorHeight, this.pixelViewport.bottom - this.pixelViewport.top);
6346 let block = map.lineAt(head, QueryType.ByPos, oracle, 0, 0), topPos;
6347 if (scrollTarget.y == "center")
6348 topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
6349 else if (scrollTarget.y == "start" || scrollTarget.y == "nearest" && head < viewport.from)
6350 topPos = block.top;
6351 else
6352 topPos = block.bottom - viewHeight;
6353 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);
6354 }
6355 }
6356 return viewport;
6357 }
6358 mapViewport(viewport, changes) {
6359 let from = changes.mapPos(viewport.from, -1), to = changes.mapPos(viewport.to, 1);
6360 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);
6361 }
6362 // Checks if a given viewport covers the visible part of the
6363 // document and not too much beyond that.
6364 viewportIsAppropriate({ from, to }, bias = 0) {
6365 if (!this.inView)
6366 return true;
6367 let { top } = this.heightMap.lineAt(from, QueryType.ByPos, this.heightOracle, 0, 0);
6368 let { bottom } = this.heightMap.lineAt(to, QueryType.ByPos, this.heightOracle, 0, 0);
6369 let { visibleTop, visibleBottom } = this;
6370 return (from == 0 || top <= visibleTop - Math.max(10 /* VP.MinCoverMargin */, Math.min(-bias, 250 /* VP.MaxCoverMargin */))) &&
6371 (to == this.state.doc.length ||
6372 bottom >= visibleBottom + Math.max(10 /* VP.MinCoverMargin */, Math.min(bias, 250 /* VP.MaxCoverMargin */))) &&
6373 (top > visibleTop - 2 * 1000 /* VP.Margin */ && bottom < visibleBottom + 2 * 1000 /* VP.Margin */);
6374 }
6375 mapLineGaps(gaps, changes) {
6376 if (!gaps.length || changes.empty)
6377 return gaps;
6378 let mapped = [];
6379 for (let gap of gaps)
6380 if (!changes.touchesRange(gap.from, gap.to))
6381 mapped.push(new LineGap(changes.mapPos(gap.from), changes.mapPos(gap.to), gap.size, gap.displaySize));
6382 return mapped;
6383 }
6384 // Computes positions in the viewport where the start or end of a
6385 // line should be hidden, trying to reuse existing line gaps when
6386 // appropriate to avoid unneccesary redraws.
6387 // Uses crude character-counting for the positioning and sizing,
6388 // since actual DOM coordinates aren't always available and
6389 // predictable. Relies on generous margins (see LG.Margin) to hide
6390 // the artifacts this might produce from the user.
6391 ensureLineGaps(current, mayMeasure) {
6392 let wrapping = this.heightOracle.lineWrapping;
6393 let margin = wrapping ? 10000 /* LG.MarginWrap */ : 2000 /* LG.Margin */, halfMargin = margin >> 1, doubleMargin = margin << 1;
6394 // The non-wrapping logic won't work at all in predominantly right-to-left text.
6395 if (this.defaultTextDirection != exports.Direction.LTR && !wrapping)
6396 return [];
6397 let gaps = [];
6398 let addGap = (from, to, line, structure) => {
6399 if (to - from < halfMargin)
6400 return;
6401 let sel = this.state.selection.main, avoid = [sel.from];
6402 if (!sel.empty)
6403 avoid.push(sel.to);
6404 for (let pos of avoid) {
6405 if (pos > from && pos < to) {
6406 addGap(from, pos - 10 /* LG.SelectionMargin */, line, structure);
6407 addGap(pos + 10 /* LG.SelectionMargin */, to, line, structure);
6408 return;
6409 }
6410 }
6411 let gap = find(current, gap => gap.from >= line.from && gap.to <= line.to &&
6412 Math.abs(gap.from - from) < halfMargin && Math.abs(gap.to - to) < halfMargin &&
6413 !avoid.some(pos => gap.from < pos && gap.to > pos));
6414 if (!gap) {
6415 // When scrolling down, snap gap ends to line starts to avoid shifts in wrapping
6416 if (to < line.to && mayMeasure && wrapping &&
6417 mayMeasure.visibleRanges.some(r => r.from <= to && r.to >= to)) {
6418 let lineStart = mayMeasure.moveToLineBoundary(state.EditorSelection.cursor(to), false, true).head;
6419 if (lineStart > from)
6420 to = lineStart;
6421 }
6422 let size = this.gapSize(line, from, to, structure);
6423 let displaySize = wrapping || size < 2000000 /* VP.MaxHorizGap */ ? size : 2000000 /* VP.MaxHorizGap */;
6424 gap = new LineGap(from, to, size, displaySize);
6425 }
6426 gaps.push(gap);
6427 };
6428 let checkLine = (line) => {
6429 if (line.length < doubleMargin || line.type != exports.BlockType.Text)
6430 return;
6431 let structure = lineStructure(line.from, line.to, this.stateDeco);
6432 if (structure.total < doubleMargin)
6433 return;
6434 let target = this.scrollTarget ? this.scrollTarget.range.head : null;
6435 let viewFrom, viewTo;
6436 if (wrapping) {
6437 let marginHeight = (margin / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
6438 let top, bot;
6439 if (target != null) {
6440 let targetFrac = findFraction(structure, target);
6441 let spaceFrac = ((this.visibleBottom - this.visibleTop) / 2 + marginHeight) / line.height;
6442 top = targetFrac - spaceFrac;
6443 bot = targetFrac + spaceFrac;
6444 }
6445 else {
6446 top = (this.visibleTop - line.top - marginHeight) / line.height;
6447 bot = (this.visibleBottom - line.top + marginHeight) / line.height;
6448 }
6449 viewFrom = findPosition(structure, top);
6450 viewTo = findPosition(structure, bot);
6451 }
6452 else {
6453 let totalWidth = structure.total * this.heightOracle.charWidth;
6454 let marginWidth = margin * this.heightOracle.charWidth;
6455 let horizOffset = 0;
6456 if (totalWidth > 2000000 /* VP.MaxHorizGap */)
6457 for (let old of current) {
6458 if (old.from >= line.from && old.from < line.to && old.size != old.displaySize &&
6459 old.from * this.heightOracle.charWidth + horizOffset < this.pixelViewport.left)
6460 horizOffset = old.size - old.displaySize;
6461 }
6462 let pxLeft = this.pixelViewport.left + horizOffset, pxRight = this.pixelViewport.right + horizOffset;
6463 let left, right;
6464 if (target != null) {
6465 let targetFrac = findFraction(structure, target);
6466 let spaceFrac = ((pxRight - pxLeft) / 2 + marginWidth) / totalWidth;
6467 left = targetFrac - spaceFrac;
6468 right = targetFrac + spaceFrac;
6469 }
6470 else {
6471 left = (pxLeft - marginWidth) / totalWidth;
6472 right = (pxRight + marginWidth) / totalWidth;
6473 }
6474 viewFrom = findPosition(structure, left);
6475 viewTo = findPosition(structure, right);
6476 }
6477 if (viewFrom > line.from)
6478 addGap(line.from, viewFrom, line, structure);
6479 if (viewTo < line.to)
6480 addGap(viewTo, line.to, line, structure);
6481 };
6482 for (let line of this.viewportLines) {
6483 if (Array.isArray(line.type))
6484 line.type.forEach(checkLine);
6485 else
6486 checkLine(line);
6487 }
6488 return gaps;
6489 }
6490 gapSize(line, from, to, structure) {
6491 let fraction = findFraction(structure, to) - findFraction(structure, from);
6492 if (this.heightOracle.lineWrapping) {
6493 return line.height * fraction;
6494 }
6495 else {
6496 return structure.total * this.heightOracle.charWidth * fraction;
6497 }
6498 }
6499 updateLineGaps(gaps) {
6500 if (!LineGap.same(gaps, this.lineGaps)) {
6501 this.lineGaps = gaps;
6502 this.lineGapDeco = Decoration.set(gaps.map(gap => gap.draw(this, this.heightOracle.lineWrapping)));
6503 }
6504 }
6505 computeVisibleRanges(changes) {
6506 let deco = this.stateDeco;
6507 if (this.lineGaps.length)
6508 deco = deco.concat(this.lineGapDeco);
6509 let ranges = [];
6510 state.RangeSet.spans(deco, this.viewport.from, this.viewport.to, {
6511 span(from, to) { ranges.push({ from, to }); },
6512 point() { }
6513 }, 20);
6514 let changed = 0;
6515 if (ranges.length != this.visibleRanges.length) {
6516 changed = 8 /* UpdateFlag.ViewportMoved */ | 4 /* UpdateFlag.Viewport */;
6517 }
6518 else {
6519 for (let i = 0; i < ranges.length && !(changed & 8 /* UpdateFlag.ViewportMoved */); i++) {
6520 let old = this.visibleRanges[i], nw = ranges[i];
6521 if (old.from != nw.from || old.to != nw.to) {
6522 changed |= 4 /* UpdateFlag.Viewport */;
6523 if (!(changes && changes.mapPos(old.from, -1) == nw.from && changes.mapPos(old.to, 1) == nw.to))
6524 changed |= 8 /* UpdateFlag.ViewportMoved */;
6525 }
6526 }
6527 }
6528 this.visibleRanges = ranges;
6529 return changed;
6530 }
6531 lineBlockAt(pos) {
6532 return (pos >= this.viewport.from && pos <= this.viewport.to &&
6533 this.viewportLines.find(b => b.from <= pos && b.to >= pos)) ||
6534 scaleBlock(this.heightMap.lineAt(pos, QueryType.ByPos, this.heightOracle, 0, 0), this.scaler);
6535 }
6536 lineBlockAtHeight(height) {
6537 return (height >= this.viewportLines[0].top && height <= this.viewportLines[this.viewportLines.length - 1].bottom &&
6538 this.viewportLines.find(l => l.top <= height && l.bottom >= height)) ||
6539 scaleBlock(this.heightMap.lineAt(this.scaler.fromDOM(height), QueryType.ByHeight, this.heightOracle, 0, 0), this.scaler);
6540 }
6541 getScrollOffset() {
6542 let base = this.scrollParent == this.view.scrollDOM ? this.scrollParent.scrollTop
6543 : (this.scrollParent ? this.scrollParent.getBoundingClientRect().top : 0) - this.view.contentDOM.getBoundingClientRect().top;
6544 return base * this.scaleY;
6545 }
6546 scrollAnchorAt(scrollOffset) {
6547 let block = this.lineBlockAtHeight(scrollOffset + 8);
6548 return block.from >= this.viewport.from || this.viewportLines[0].top - scrollOffset > 200 ? block : this.viewportLines[0];
6549 }
6550 elementAtHeight(height) {
6551 return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height), this.heightOracle, 0, 0), this.scaler);
6552 }
6553 get docHeight() {
6554 return this.scaler.toDOM(this.heightMap.height);
6555 }
6556 get contentHeight() {
6557 return this.docHeight + this.paddingTop + this.paddingBottom;
6558 }
6559}
6560class Viewport {
6561 constructor(from, to) {
6562 this.from = from;
6563 this.to = to;
6564 }
6565}
6566function lineStructure(from, to, stateDeco) {
6567 let ranges = [], pos = from, total = 0;
6568 state.RangeSet.spans(stateDeco, from, to, {
6569 span() { },
6570 point(from, to) {
6571 if (from > pos) {
6572 ranges.push({ from: pos, to: from });
6573 total += from - pos;
6574 }
6575 pos = to;
6576 }
6577 }, 20); // We're only interested in collapsed ranges of a significant size
6578 if (pos < to) {
6579 ranges.push({ from: pos, to });
6580 total += to - pos;
6581 }
6582 return { total, ranges };
6583}
6584function findPosition({ total, ranges }, ratio) {
6585 if (ratio <= 0)
6586 return ranges[0].from;
6587 if (ratio >= 1)
6588 return ranges[ranges.length - 1].to;
6589 let dist = Math.floor(total * ratio);
6590 for (let i = 0;; i++) {
6591 let { from, to } = ranges[i], size = to - from;
6592 if (dist <= size)
6593 return from + dist;
6594 dist -= size;
6595 }
6596}
6597function findFraction(structure, pos) {
6598 let counted = 0;
6599 for (let { from, to } of structure.ranges) {
6600 if (pos <= to) {
6601 counted += pos - from;
6602 break;
6603 }
6604 counted += to - from;
6605 }
6606 return counted / structure.total;
6607}
6608function find(array, f) {
6609 for (let val of array)
6610 if (f(val))
6611 return val;
6612 return undefined;
6613}
6614// Don't scale when the document height is within the range of what
6615// the DOM can handle.
6616const IdScaler = {
6617 toDOM(n) { return n; },
6618 fromDOM(n) { return n; },
6619 scale: 1,
6620 eq(other) { return other == this; }
6621};
6622function staticDeco(state$1) {
6623 let deco = state$1.facet(decorations).filter(d => typeof d != "function");
6624 let outer = state$1.facet(outerDecorations).filter(d => typeof d != "function");
6625 if (outer.length)
6626 deco.push(state.RangeSet.join(outer));
6627 return deco;
6628}
6629// When the height is too big (> VP.MaxDOMHeight), scale down the
6630// regions outside the viewports so that the total height is
6631// VP.MaxDOMHeight.
6632class BigScaler {
6633 constructor(oracle, heightMap, viewports) {
6634 let vpHeight = 0, base = 0, domBase = 0;
6635 this.viewports = viewports.map(({ from, to }) => {
6636 let top = heightMap.lineAt(from, QueryType.ByPos, oracle, 0, 0).top;
6637 let bottom = heightMap.lineAt(to, QueryType.ByPos, oracle, 0, 0).bottom;
6638 vpHeight += bottom - top;
6639 return { from, to, top, bottom, domTop: 0, domBottom: 0 };
6640 });
6641 this.scale = (7000000 /* VP.MaxDOMHeight */ - vpHeight) / (heightMap.height - vpHeight);
6642 for (let obj of this.viewports) {
6643 obj.domTop = domBase + (obj.top - base) * this.scale;
6644 domBase = obj.domBottom = obj.domTop + (obj.bottom - obj.top);
6645 base = obj.bottom;
6646 }
6647 }
6648 toDOM(n) {
6649 for (let i = 0, base = 0, domBase = 0;; i++) {
6650 let vp = i < this.viewports.length ? this.viewports[i] : null;
6651 if (!vp || n < vp.top)
6652 return domBase + (n - base) * this.scale;
6653 if (n <= vp.bottom)
6654 return vp.domTop + (n - vp.top);
6655 base = vp.bottom;
6656 domBase = vp.domBottom;
6657 }
6658 }
6659 fromDOM(n) {
6660 for (let i = 0, base = 0, domBase = 0;; i++) {
6661 let vp = i < this.viewports.length ? this.viewports[i] : null;
6662 if (!vp || n < vp.domTop)
6663 return base + (n - domBase) / this.scale;
6664 if (n <= vp.domBottom)
6665 return vp.top + (n - vp.domTop);
6666 base = vp.bottom;
6667 domBase = vp.domBottom;
6668 }
6669 }
6670 eq(other) {
6671 if (!(other instanceof BigScaler))
6672 return false;
6673 return this.scale == other.scale && this.viewports.length == other.viewports.length &&
6674 this.viewports.every((vp, i) => vp.from == other.viewports[i].from && vp.to == other.viewports[i].to);
6675 }
6676}
6677function scaleBlock(block, scaler) {
6678 if (scaler.scale == 1)
6679 return block;
6680 let bTop = scaler.toDOM(block.top), bBottom = scaler.toDOM(block.bottom);
6681 return new BlockInfo(block.from, block.length, bTop, bBottom - bTop, Array.isArray(block._content) ? block._content.map(b => scaleBlock(b, scaler)) : block._content);
6682}
6683
6684const theme = state.Facet.define({ combine: strs => strs.join(" ") });
6685const darkTheme = state.Facet.define({ combine: values => values.indexOf(true) > -1 });
6686const baseThemeID = styleMod.StyleModule.newName(), baseLightID = styleMod.StyleModule.newName(), baseDarkID = styleMod.StyleModule.newName();
6687const lightDarkIDs = { "&light": "." + baseLightID, "&dark": "." + baseDarkID };
6688function buildTheme(main, spec, scopes) {
6689 return new styleMod.StyleModule(spec, {
6690 finish(sel) {
6691 return /&/.test(sel) ? sel.replace(/&\w*/, m => {
6692 if (m == "&")
6693 return main;
6694 if (!scopes || !scopes[m])
6695 throw new RangeError(`Unsupported selector: ${m}`);
6696 return scopes[m];
6697 }) : main + " " + sel;
6698 }
6699 });
6700}
6701const baseTheme$1 = buildTheme("." + baseThemeID, {
6702 "&": {
6703 position: "relative !important",
6704 boxSizing: "border-box",
6705 "&.cm-focused": {
6706 // Provide a simple default outline to make sure a focused
6707 // editor is visually distinct. Can't leave the default behavior
6708 // because that will apply to the content element, which is
6709 // inside the scrollable container and doesn't include the
6710 // gutters. We also can't use an 'auto' outline, since those
6711 // are, for some reason, drawn behind the element content, which
6712 // will cause things like the active line background to cover
6713 // the outline (#297).
6714 outline: "1px dotted #212121"
6715 },
6716 display: "flex !important",
6717 flexDirection: "column"
6718 },
6719 ".cm-scroller": {
6720 display: "flex !important",
6721 alignItems: "flex-start !important",
6722 fontFamily: "monospace",
6723 lineHeight: 1.4,
6724 height: "100%",
6725 overflowX: "auto",
6726 position: "relative",
6727 zIndex: 0,
6728 overflowAnchor: "none",
6729 },
6730 ".cm-content": {
6731 margin: 0,
6732 flexGrow: 2,
6733 flexShrink: 0,
6734 display: "block",
6735 whiteSpace: "pre",
6736 wordWrap: "normal", // https://github.com/codemirror/dev/issues/456
6737 boxSizing: "border-box",
6738 minHeight: "100%",
6739 padding: "4px 0",
6740 outline: "none",
6741 "&[contenteditable=true]": {
6742 WebkitUserModify: "read-write-plaintext-only",
6743 }
6744 },
6745 ".cm-lineWrapping": {
6746 whiteSpace_fallback: "pre-wrap", // For IE
6747 whiteSpace: "break-spaces",
6748 wordBreak: "break-word", // For Safari, which doesn't support overflow-wrap: anywhere
6749 overflowWrap: "anywhere",
6750 flexShrink: 1
6751 },
6752 "&light .cm-content": { caretColor: "black" },
6753 "&dark .cm-content": { caretColor: "white" },
6754 ".cm-line": {
6755 display: "block",
6756 padding: "0 2px 0 6px"
6757 },
6758 ".cm-layer": {
6759 position: "absolute",
6760 left: 0,
6761 top: 0,
6762 contain: "size style",
6763 "& > *": {
6764 position: "absolute"
6765 }
6766 },
6767 "&light .cm-selectionBackground": {
6768 background: "#d9d9d9"
6769 },
6770 "&dark .cm-selectionBackground": {
6771 background: "#222"
6772 },
6773 "&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
6774 background: "#d7d4f0"
6775 },
6776 "&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
6777 background: "#233"
6778 },
6779 ".cm-cursorLayer": {
6780 pointerEvents: "none"
6781 },
6782 "&.cm-focused > .cm-scroller > .cm-cursorLayer": {
6783 animation: "steps(1) cm-blink 1.2s infinite"
6784 },
6785 // Two animations defined so that we can switch between them to
6786 // restart the animation without forcing another style
6787 // recomputation.
6788 "@keyframes cm-blink": { "0%": {}, "50%": { opacity: 0 }, "100%": {} },
6789 "@keyframes cm-blink2": { "0%": {}, "50%": { opacity: 0 }, "100%": {} },
6790 ".cm-cursor, .cm-dropCursor": {
6791 borderLeft: "1.2px solid black",
6792 marginLeft: "-0.6px",
6793 pointerEvents: "none",
6794 },
6795 ".cm-cursor": {
6796 display: "none"
6797 },
6798 "&dark .cm-cursor": {
6799 borderLeftColor: "#ddd"
6800 },
6801 ".cm-dropCursor": {
6802 position: "absolute"
6803 },
6804 "&.cm-focused > .cm-scroller > .cm-cursorLayer .cm-cursor": {
6805 display: "block"
6806 },
6807 ".cm-iso": {
6808 unicodeBidi: "isolate"
6809 },
6810 ".cm-announced": {
6811 position: "fixed",
6812 top: "-10000px"
6813 },
6814 "@media print": {
6815 ".cm-announced": { display: "none" }
6816 },
6817 "&light .cm-activeLine": { backgroundColor: "#cceeff44" },
6818 "&dark .cm-activeLine": { backgroundColor: "#99eeff33" },
6819 "&light .cm-specialChar": { color: "red" },
6820 "&dark .cm-specialChar": { color: "#f78" },
6821 ".cm-gutters": {
6822 flexShrink: 0,
6823 display: "flex",
6824 height: "100%",
6825 boxSizing: "border-box",
6826 zIndex: 200,
6827 },
6828 ".cm-gutters-before": { insetInlineStart: 0 },
6829 ".cm-gutters-after": { insetInlineEnd: 0 },
6830 "&light .cm-gutters": {
6831 backgroundColor: "#f5f5f5",
6832 color: "#6c6c6c",
6833 border: "0px solid #ddd",
6834 "&.cm-gutters-before": { borderRightWidth: "1px" },
6835 "&.cm-gutters-after": { borderLeftWidth: "1px" },
6836 },
6837 "&dark .cm-gutters": {
6838 backgroundColor: "#333338",
6839 color: "#ccc"
6840 },
6841 ".cm-gutter": {
6842 display: "flex !important", // Necessary -- prevents margin collapsing
6843 flexDirection: "column",
6844 flexShrink: 0,
6845 boxSizing: "border-box",
6846 minHeight: "100%",
6847 overflow: "hidden"
6848 },
6849 ".cm-gutterElement": {
6850 boxSizing: "border-box"
6851 },
6852 ".cm-lineNumbers .cm-gutterElement": {
6853 padding: "0 3px 0 5px",
6854 minWidth: "20px",
6855 textAlign: "right",
6856 whiteSpace: "nowrap"
6857 },
6858 "&light .cm-activeLineGutter": {
6859 backgroundColor: "#e2f2ff"
6860 },
6861 "&dark .cm-activeLineGutter": {
6862 backgroundColor: "#222227"
6863 },
6864 ".cm-panels": {
6865 boxSizing: "border-box",
6866 position: "sticky",
6867 left: 0,
6868 right: 0,
6869 zIndex: 300
6870 },
6871 "&light .cm-panels": {
6872 backgroundColor: "#f5f5f5",
6873 color: "black"
6874 },
6875 "&light .cm-panels-top": {
6876 borderBottom: "1px solid #ddd"
6877 },
6878 "&light .cm-panels-bottom": {
6879 borderTop: "1px solid #ddd"
6880 },
6881 "&dark .cm-panels": {
6882 backgroundColor: "#333338",
6883 color: "white"
6884 },
6885 ".cm-dialog": {
6886 padding: "2px 19px 4px 6px",
6887 position: "relative",
6888 "& label": { fontSize: "80%" },
6889 },
6890 ".cm-dialog-close": {
6891 position: "absolute",
6892 top: "3px",
6893 right: "4px",
6894 backgroundColor: "inherit",
6895 border: "none",
6896 font: "inherit",
6897 fontSize: "14px",
6898 padding: "0"
6899 },
6900 ".cm-tab": {
6901 display: "inline-block",
6902 overflow: "hidden",
6903 verticalAlign: "bottom"
6904 },
6905 ".cm-widgetBuffer": {
6906 verticalAlign: "text-top",
6907 height: "1em",
6908 width: 0,
6909 display: "inline"
6910 },
6911 ".cm-placeholder": {
6912 color: "#888",
6913 display: "inline-block",
6914 verticalAlign: "top",
6915 userSelect: "none"
6916 },
6917 ".cm-highlightSpace": {
6918 backgroundImage: "radial-gradient(circle at 50% 55%, #aaa 20%, transparent 5%)",
6919 backgroundPosition: "center",
6920 },
6921 ".cm-highlightTab": {
6922 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>')`,
6923 backgroundSize: "auto 100%",
6924 backgroundPosition: "right 90%",
6925 backgroundRepeat: "no-repeat"
6926 },
6927 ".cm-trailingSpace": {
6928 backgroundColor: "#ff332255"
6929 },
6930 ".cm-button": {
6931 verticalAlign: "middle",
6932 color: "inherit",
6933 fontSize: "70%",
6934 padding: ".2em 1em",
6935 borderRadius: "1px"
6936 },
6937 "&light .cm-button": {
6938 backgroundImage: "linear-gradient(#eff1f5, #d9d9df)",
6939 border: "1px solid #888",
6940 "&:active": {
6941 backgroundImage: "linear-gradient(#b4b4b4, #d0d3d6)"
6942 }
6943 },
6944 "&dark .cm-button": {
6945 backgroundImage: "linear-gradient(#393939, #111)",
6946 border: "1px solid #888",
6947 "&:active": {
6948 backgroundImage: "linear-gradient(#111, #333)"
6949 }
6950 },
6951 ".cm-textfield": {
6952 verticalAlign: "middle",
6953 color: "inherit",
6954 fontSize: "70%",
6955 border: "1px solid silver",
6956 padding: ".2em .5em"
6957 },
6958 "&light .cm-textfield": {
6959 backgroundColor: "white"
6960 },
6961 "&dark .cm-textfield": {
6962 border: "1px solid #555",
6963 backgroundColor: "inherit"
6964 }
6965}, lightDarkIDs);
6966
6967const observeOptions = {
6968 childList: true,
6969 characterData: true,
6970 subtree: true,
6971 attributes: true,
6972 characterDataOldValue: true
6973};
6974// IE11 has very broken mutation observers, so we also listen to
6975// DOMCharacterDataModified there
6976const useCharData = browser.ie && browser.ie_version <= 11;
6977class DOMObserver {
6978 constructor(view) {
6979 this.view = view;
6980 this.active = false;
6981 this.editContext = null;
6982 // The known selection. Kept in our own object, as opposed to just
6983 // directly accessing the selection because:
6984 // - Safari doesn't report the right selection in shadow DOM
6985 // - Reading from the selection forces a DOM layout
6986 // - This way, we can ignore selectionchange events if we have
6987 // already seen the 'new' selection
6988 this.selectionRange = new DOMSelectionState;
6989 // Set when a selection change is detected, cleared on flush
6990 this.selectionChanged = false;
6991 this.delayedFlush = -1;
6992 this.resizeTimeout = -1;
6993 this.queue = [];
6994 this.delayedAndroidKey = null;
6995 this.flushingAndroidKey = -1;
6996 this.lastChange = 0;
6997 this.scrollTargets = [];
6998 this.intersection = null;
6999 this.resizeScroll = null;
7000 this.intersecting = false;
7001 this.gapIntersection = null;
7002 this.gaps = [];
7003 this.printQuery = null;
7004 // Timeout for scheduling check of the parents that need scroll handlers
7005 this.parentCheck = -1;
7006 this.dom = view.contentDOM;
7007 this.observer = new MutationObserver(mutations => {
7008 for (let mut of mutations)
7009 this.queue.push(mut);
7010 // IE11 will sometimes (on typing over a selection or
7011 // backspacing out a single character text node) call the
7012 // observer callback before actually updating the DOM.
7013 //
7014 // Unrelatedly, iOS Safari will, when ending a composition,
7015 // sometimes first clear it, deliver the mutations, and then
7016 // reinsert the finished text. CodeMirror's handling of the
7017 // deletion will prevent the reinsertion from happening,
7018 // breaking composition.
7019 if ((browser.ie && browser.ie_version <= 11 || browser.ios && view.composing) &&
7020 mutations.some(m => m.type == "childList" && m.removedNodes.length ||
7021 m.type == "characterData" && m.oldValue.length > m.target.nodeValue.length))
7022 this.flushSoon();
7023 else
7024 this.flush();
7025 });
7026 if (window.EditContext && browser.android && view.constructor.EDIT_CONTEXT !== false &&
7027 // Chrome <126 doesn't support inverted selections in edit context (#1392)
7028 !(browser.chrome && browser.chrome_version < 126)) {
7029 this.editContext = new EditContextManager(view);
7030 if (view.state.facet(editable))
7031 view.contentDOM.editContext = this.editContext.editContext;
7032 }
7033 if (useCharData)
7034 this.onCharData = (event) => {
7035 this.queue.push({ target: event.target,
7036 type: "characterData",
7037 oldValue: event.prevValue });
7038 this.flushSoon();
7039 };
7040 this.onSelectionChange = this.onSelectionChange.bind(this);
7041 this.onResize = this.onResize.bind(this);
7042 this.onPrint = this.onPrint.bind(this);
7043 this.onScroll = this.onScroll.bind(this);
7044 if (window.matchMedia)
7045 this.printQuery = window.matchMedia("print");
7046 if (typeof ResizeObserver == "function") {
7047 this.resizeScroll = new ResizeObserver(() => {
7048 var _a;
7049 if (((_a = this.view.docView) === null || _a === void 0 ? void 0 : _a.lastUpdate) < Date.now() - 75)
7050 this.onResize();
7051 });
7052 this.resizeScroll.observe(view.scrollDOM);
7053 }
7054 this.addWindowListeners(this.win = view.win);
7055 this.start();
7056 if (typeof IntersectionObserver == "function") {
7057 this.intersection = new IntersectionObserver(entries => {
7058 if (this.parentCheck < 0)
7059 this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1000);
7060 if (entries.length > 0 && (entries[entries.length - 1].intersectionRatio > 0) != this.intersecting) {
7061 this.intersecting = !this.intersecting;
7062 if (this.intersecting != this.view.inView)
7063 this.onScrollChanged(document.createEvent("Event"));
7064 }
7065 }, { threshold: [0, .001] });
7066 this.intersection.observe(this.dom);
7067 this.gapIntersection = new IntersectionObserver(entries => {
7068 if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0)
7069 this.onScrollChanged(document.createEvent("Event"));
7070 }, {});
7071 }
7072 this.listenForScroll();
7073 this.readSelectionRange();
7074 }
7075 onScrollChanged(e) {
7076 this.view.inputState.runHandlers("scroll", e);
7077 if (this.intersecting)
7078 this.view.measure();
7079 }
7080 onScroll(e) {
7081 if (this.intersecting)
7082 this.flush(false);
7083 if (this.editContext)
7084 this.view.requestMeasure(this.editContext.measureReq);
7085 this.onScrollChanged(e);
7086 }
7087 onResize() {
7088 if (this.resizeTimeout < 0)
7089 this.resizeTimeout = setTimeout(() => {
7090 this.resizeTimeout = -1;
7091 this.view.requestMeasure();
7092 }, 50);
7093 }
7094 onPrint(event) {
7095 if ((event.type == "change" || !event.type) && !event.matches)
7096 return;
7097 this.view.viewState.printing = true;
7098 this.view.measure();
7099 setTimeout(() => {
7100 this.view.viewState.printing = false;
7101 this.view.requestMeasure();
7102 }, 500);
7103 }
7104 updateGaps(gaps) {
7105 if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
7106 this.gapIntersection.disconnect();
7107 for (let gap of gaps)
7108 this.gapIntersection.observe(gap);
7109 this.gaps = gaps;
7110 }
7111 }
7112 onSelectionChange(event) {
7113 let wasChanged = this.selectionChanged;
7114 if (!this.readSelectionRange() || this.delayedAndroidKey)
7115 return;
7116 let { view } = this, sel = this.selectionRange;
7117 if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(this.dom, sel))
7118 return;
7119 let context = sel.anchorNode && view.docView.tile.nearest(sel.anchorNode);
7120 if (context && context.isWidget() && context.widget.ignoreEvent(event)) {
7121 if (!wasChanged)
7122 this.selectionChanged = false;
7123 return;
7124 }
7125 // Deletions on IE11 fire their events in the wrong order, giving
7126 // us a selection change event before the DOM changes are
7127 // reported.
7128 // Chrome Android has a similar issue when backspacing out a
7129 // selection (#645).
7130 if ((browser.ie && browser.ie_version <= 11 || browser.android && browser.chrome) && !view.state.selection.main.empty &&
7131 // (Selection.isCollapsed isn't reliable on IE)
7132 sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset))
7133 this.flushSoon();
7134 else
7135 this.flush(false);
7136 }
7137 readSelectionRange() {
7138 let { view } = this;
7139 // The Selection object is broken in shadow roots in Safari. See
7140 // https://github.com/codemirror/dev/issues/414
7141 let selection = getSelection(view.root);
7142 if (!selection)
7143 return false;
7144 let range = browser.safari && view.root.nodeType == 11 &&
7145 view.root.activeElement == this.dom &&
7146 safariSelectionRangeHack(this.view, selection) || selection;
7147 if (!range || this.selectionRange.eq(range))
7148 return false;
7149 let local = hasSelection(this.dom, range);
7150 // Detect the situation where the browser has, on focus, moved the
7151 // selection to the start of the content element. Reset it to the
7152 // position from the editor state.
7153 if (local && !this.selectionChanged &&
7154 view.inputState.lastFocusTime > Date.now() - 200 &&
7155 view.inputState.lastTouchTime < Date.now() - 300 &&
7156 atElementStart(this.dom, range)) {
7157 this.view.inputState.lastFocusTime = 0;
7158 view.docView.updateSelection();
7159 return false;
7160 }
7161 this.selectionRange.setRange(range);
7162 if (local)
7163 this.selectionChanged = true;
7164 return true;
7165 }
7166 setSelectionRange(anchor, head) {
7167 this.selectionRange.set(anchor.node, anchor.offset, head.node, head.offset);
7168 this.selectionChanged = false;
7169 }
7170 clearSelectionRange() {
7171 this.selectionRange.set(null, 0, null, 0);
7172 }
7173 listenForScroll() {
7174 this.parentCheck = -1;
7175 let i = 0, changed = null;
7176 for (let dom = this.dom; dom;) {
7177 if (dom.nodeType == 1) {
7178 if (!changed && i < this.scrollTargets.length && this.scrollTargets[i] == dom)
7179 i++;
7180 else if (!changed)
7181 changed = this.scrollTargets.slice(0, i);
7182 if (changed)
7183 changed.push(dom);
7184 dom = dom.assignedSlot || dom.parentNode;
7185 }
7186 else if (dom.nodeType == 11) { // Shadow root
7187 dom = dom.host;
7188 }
7189 else {
7190 break;
7191 }
7192 }
7193 if (i < this.scrollTargets.length && !changed)
7194 changed = this.scrollTargets.slice(0, i);
7195 if (changed) {
7196 for (let dom of this.scrollTargets)
7197 dom.removeEventListener("scroll", this.onScroll);
7198 for (let dom of this.scrollTargets = changed)
7199 dom.addEventListener("scroll", this.onScroll);
7200 }
7201 }
7202 ignore(f) {
7203 if (!this.active)
7204 return f();
7205 try {
7206 this.stop();
7207 return f();
7208 }
7209 finally {
7210 this.start();
7211 this.clear();
7212 }
7213 }
7214 start() {
7215 if (this.active)
7216 return;
7217 this.observer.observe(this.dom, observeOptions);
7218 if (useCharData)
7219 this.dom.addEventListener("DOMCharacterDataModified", this.onCharData);
7220 this.active = true;
7221 }
7222 stop() {
7223 if (!this.active)
7224 return;
7225 this.active = false;
7226 this.observer.disconnect();
7227 if (useCharData)
7228 this.dom.removeEventListener("DOMCharacterDataModified", this.onCharData);
7229 }
7230 // Throw away any pending changes
7231 clear() {
7232 this.processRecords();
7233 this.queue.length = 0;
7234 this.selectionChanged = false;
7235 }
7236 // Chrome Android, especially in combination with GBoard, not only
7237 // doesn't reliably fire regular key events, but also often
7238 // surrounds the effect of enter or backspace with a bunch of
7239 // composition events that, when interrupted, cause text duplication
7240 // or other kinds of corruption. This hack makes the editor back off
7241 // from handling DOM changes for a moment when such a key is
7242 // detected (via beforeinput or keydown), and then tries to flush
7243 // them or, if that has no effect, dispatches the given key.
7244 delayAndroidKey(key, keyCode) {
7245 var _a;
7246 if (!this.delayedAndroidKey) {
7247 let flush = () => {
7248 let key = this.delayedAndroidKey;
7249 if (key) {
7250 this.clearDelayedAndroidKey();
7251 this.view.inputState.lastKeyCode = key.keyCode;
7252 this.view.inputState.lastKeyTime = Date.now();
7253 let flushed = this.flush();
7254 if (!flushed && key.force)
7255 dispatchKey(this.dom, key.key, key.keyCode);
7256 }
7257 };
7258 this.flushingAndroidKey = this.view.win.requestAnimationFrame(flush);
7259 }
7260 // Since backspace beforeinput is sometimes signalled spuriously,
7261 // Enter always takes precedence.
7262 if (!this.delayedAndroidKey || key == "Enter")
7263 this.delayedAndroidKey = {
7264 key, keyCode,
7265 // Only run the key handler when no changes are detected if
7266 // this isn't coming right after another change, in which case
7267 // it is probably part of a weird chain of updates, and should
7268 // be ignored if it returns the DOM to its previous state.
7269 force: this.lastChange < Date.now() - 50 || !!((_a = this.delayedAndroidKey) === null || _a === void 0 ? void 0 : _a.force)
7270 };
7271 }
7272 clearDelayedAndroidKey() {
7273 this.win.cancelAnimationFrame(this.flushingAndroidKey);
7274 this.delayedAndroidKey = null;
7275 this.flushingAndroidKey = -1;
7276 }
7277 flushSoon() {
7278 if (this.delayedFlush < 0)
7279 this.delayedFlush = this.view.win.requestAnimationFrame(() => { this.delayedFlush = -1; this.flush(); });
7280 }
7281 forceFlush() {
7282 if (this.delayedFlush >= 0) {
7283 this.view.win.cancelAnimationFrame(this.delayedFlush);
7284 this.delayedFlush = -1;
7285 }
7286 this.flush();
7287 }
7288 pendingRecords() {
7289 for (let mut of this.observer.takeRecords())
7290 this.queue.push(mut);
7291 return this.queue;
7292 }
7293 processRecords() {
7294 let records = this.pendingRecords();
7295 if (records.length)
7296 this.queue = [];
7297 let from = -1, to = -1, typeOver = false;
7298 for (let record of records) {
7299 let range = this.readMutation(record);
7300 if (!range)
7301 continue;
7302 if (range.typeOver)
7303 typeOver = true;
7304 if (from == -1) {
7305 ({ from, to } = range);
7306 }
7307 else {
7308 from = Math.min(range.from, from);
7309 to = Math.max(range.to, to);
7310 }
7311 }
7312 return { from, to, typeOver };
7313 }
7314 readChange() {
7315 let { from, to, typeOver } = this.processRecords();
7316 let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
7317 if (from < 0 && !newSel)
7318 return null;
7319 if (from > -1)
7320 this.lastChange = Date.now();
7321 this.view.inputState.lastFocusTime = 0;
7322 this.selectionChanged = false;
7323 let change = new DOMChange(this.view, from, to, typeOver);
7324 this.view.docView.domChanged = { newSel: change.newSel ? change.newSel.main : null };
7325 return change;
7326 }
7327 // Apply pending changes, if any
7328 flush(readSelection = true) {
7329 // Completely hold off flushing when pending keys are set—the code
7330 // managing those will make sure processRecords is called and the
7331 // view is resynchronized after
7332 if (this.delayedFlush >= 0 || this.delayedAndroidKey)
7333 return false;
7334 if (readSelection)
7335 this.readSelectionRange();
7336 let domChange = this.readChange();
7337 if (!domChange) {
7338 this.view.requestMeasure();
7339 return false;
7340 }
7341 let startState = this.view.state;
7342 let handled = applyDOMChange(this.view, domChange);
7343 // The view wasn't updated but DOM/selection changes were seen. Reset the view.
7344 if (this.view.state == startState &&
7345 (domChange.domChanged || domChange.newSel && !sameSelPos(this.view.state.selection, domChange.newSel.main)))
7346 this.view.update([]);
7347 return handled;
7348 }
7349 readMutation(rec) {
7350 let tile = this.view.docView.tile.nearest(rec.target);
7351 if (!tile || tile.isWidget())
7352 return null;
7353 tile.markDirty(rec.type == "attributes");
7354 if (rec.type == "childList") {
7355 let childBefore = findChild(tile, rec.previousSibling || rec.target.previousSibling, -1);
7356 let childAfter = findChild(tile, rec.nextSibling || rec.target.nextSibling, 1);
7357 return { from: childBefore ? tile.posAfter(childBefore) : tile.posAtStart,
7358 to: childAfter ? tile.posBefore(childAfter) : tile.posAtEnd, typeOver: false };
7359 }
7360 else if (rec.type == "characterData") {
7361 return { from: tile.posAtStart, to: tile.posAtEnd, typeOver: rec.target.nodeValue == rec.oldValue };
7362 }
7363 else {
7364 return null;
7365 }
7366 }
7367 setWindow(win) {
7368 if (win != this.win) {
7369 this.removeWindowListeners(this.win);
7370 this.win = win;
7371 this.addWindowListeners(this.win);
7372 }
7373 }
7374 addWindowListeners(win) {
7375 win.addEventListener("resize", this.onResize);
7376 if (this.printQuery) {
7377 if (this.printQuery.addEventListener)
7378 this.printQuery.addEventListener("change", this.onPrint);
7379 else
7380 this.printQuery.addListener(this.onPrint);
7381 }
7382 else
7383 win.addEventListener("beforeprint", this.onPrint);
7384 win.addEventListener("scroll", this.onScroll);
7385 win.document.addEventListener("selectionchange", this.onSelectionChange);
7386 }
7387 removeWindowListeners(win) {
7388 win.removeEventListener("scroll", this.onScroll);
7389 win.removeEventListener("resize", this.onResize);
7390 if (this.printQuery) {
7391 if (this.printQuery.removeEventListener)
7392 this.printQuery.removeEventListener("change", this.onPrint);
7393 else
7394 this.printQuery.removeListener(this.onPrint);
7395 }
7396 else
7397 win.removeEventListener("beforeprint", this.onPrint);
7398 win.document.removeEventListener("selectionchange", this.onSelectionChange);
7399 }
7400 update(update) {
7401 if (this.editContext) {
7402 this.editContext.update(update);
7403 if (update.startState.facet(editable) != update.state.facet(editable))
7404 update.view.contentDOM.editContext = update.state.facet(editable) ? this.editContext.editContext : null;
7405 }
7406 }
7407 destroy() {
7408 var _a, _b, _c;
7409 this.stop();
7410 (_a = this.intersection) === null || _a === void 0 ? void 0 : _a.disconnect();
7411 (_b = this.gapIntersection) === null || _b === void 0 ? void 0 : _b.disconnect();
7412 (_c = this.resizeScroll) === null || _c === void 0 ? void 0 : _c.disconnect();
7413 for (let dom of this.scrollTargets)
7414 dom.removeEventListener("scroll", this.onScroll);
7415 this.removeWindowListeners(this.win);
7416 clearTimeout(this.parentCheck);
7417 clearTimeout(this.resizeTimeout);
7418 this.win.cancelAnimationFrame(this.delayedFlush);
7419 this.win.cancelAnimationFrame(this.flushingAndroidKey);
7420 if (this.editContext) {
7421 this.view.contentDOM.editContext = null;
7422 this.editContext.destroy();
7423 }
7424 }
7425}
7426function findChild(tile, dom, dir) {
7427 while (dom) {
7428 let curTile = Tile.get(dom);
7429 if (curTile && curTile.parent == tile)
7430 return curTile;
7431 let parent = dom.parentNode;
7432 dom = parent != tile.dom ? parent : dir > 0 ? dom.nextSibling : dom.previousSibling;
7433 }
7434 return null;
7435}
7436function buildSelectionRangeFromRange(view, range) {
7437 let anchorNode = range.startContainer, anchorOffset = range.startOffset;
7438 let focusNode = range.endContainer, focusOffset = range.endOffset;
7439 let curAnchor = view.docView.domAtPos(view.state.selection.main.anchor, 1);
7440 // Since such a range doesn't distinguish between anchor and head,
7441 // use a heuristic that flips it around if its end matches the
7442 // current anchor.
7443 if (isEquivalentPosition(curAnchor.node, curAnchor.offset, focusNode, focusOffset))
7444 [anchorNode, anchorOffset, focusNode, focusOffset] = [focusNode, focusOffset, anchorNode, anchorOffset];
7445 return { anchorNode, anchorOffset, focusNode, focusOffset };
7446}
7447// Used to work around a Safari Selection/shadow DOM bug (#414)
7448function safariSelectionRangeHack(view, selection) {
7449 if (selection.getComposedRanges) {
7450 let range = selection.getComposedRanges(view.root)[0];
7451 if (range)
7452 return buildSelectionRangeFromRange(view, range);
7453 }
7454 let found = null;
7455 // Because Safari (at least in 2018-2021) doesn't provide regular
7456 // access to the selection inside a shadowroot, we have to perform a
7457 // ridiculous hack to get at it—using `execCommand` to trigger a
7458 // `beforeInput` event so that we can read the target range from the
7459 // event.
7460 function read(event) {
7461 event.preventDefault();
7462 event.stopImmediatePropagation();
7463 found = event.getTargetRanges()[0];
7464 }
7465 view.contentDOM.addEventListener("beforeinput", read, true);
7466 view.dom.ownerDocument.execCommand("indent");
7467 view.contentDOM.removeEventListener("beforeinput", read, true);
7468 return found ? buildSelectionRangeFromRange(view, found) : null;
7469}
7470class EditContextManager {
7471 constructor(view) {
7472 // The document window for which the text in the context is
7473 // maintained. For large documents, this may be smaller than the
7474 // editor document. This window always includes the selection head.
7475 this.from = 0;
7476 this.to = 0;
7477 // When applying a transaction, this is used to compare the change
7478 // made to the context content to the change in the transaction in
7479 // order to make the minimal changes to the context (since touching
7480 // that sometimes breaks series of multiple edits made for a single
7481 // user action on some Android keyboards)
7482 this.pendingContextChange = null;
7483 this.handlers = Object.create(null);
7484 // Kludge to work around the fact that EditContext does not respond
7485 // well to having its content updated during a composition (see #1472)
7486 this.composing = null;
7487 this.resetRange(view.state);
7488 let context = this.editContext = new window.EditContext({
7489 text: view.state.doc.sliceString(this.from, this.to),
7490 selectionStart: this.toContextPos(Math.max(this.from, Math.min(this.to, view.state.selection.main.anchor))),
7491 selectionEnd: this.toContextPos(view.state.selection.main.head)
7492 });
7493 this.handlers.textupdate = e => {
7494 let main = view.state.selection.main, { anchor, head } = main;
7495 let from = this.toEditorPos(e.updateRangeStart), to = this.toEditorPos(e.updateRangeEnd);
7496 if (view.inputState.composing >= 0 && !this.composing)
7497 this.composing = { contextBase: e.updateRangeStart, editorBase: from, drifted: false };
7498 let deletes = to - from > e.text.length;
7499 // If the window doesn't include the anchor, assume changes
7500 // adjacent to a side go up to the anchor.
7501 if (from == this.from && anchor < this.from)
7502 from = anchor;
7503 else if (to == this.to && anchor > this.to)
7504 to = anchor;
7505 let diff = findDiff(view.state.sliceDoc(from, to), e.text, (deletes ? main.from : main.to) - from, deletes ? "end" : null);
7506 // Edit contexts sometimes fire empty changes
7507 if (!diff) {
7508 let newSel = state.EditorSelection.single(this.toEditorPos(e.selectionStart), this.toEditorPos(e.selectionEnd));
7509 if (!sameSelPos(newSel, main))
7510 view.dispatch({ selection: newSel, userEvent: "select" });
7511 return;
7512 }
7513 let change = { from: diff.from + from, to: diff.toA + from,
7514 insert: state.Text.of(e.text.slice(diff.from, diff.toB).split("\n")) };
7515 if ((browser.mac || browser.android) && change.from == head - 1 &&
7516 /^\. ?$/.test(e.text) && view.contentDOM.getAttribute("autocorrect") == "off")
7517 change = { from, to, insert: state.Text.of([e.text.replace(".", " ")]) };
7518 this.pendingContextChange = change;
7519 if (!view.state.readOnly) {
7520 let newLen = this.to - this.from + (change.to - change.from + change.insert.length);
7521 applyDOMChangeInner(view, change, state.EditorSelection.single(this.toEditorPos(e.selectionStart, newLen), this.toEditorPos(e.selectionEnd, newLen)));
7522 }
7523 // If the transaction didn't flush our change, revert it so
7524 // that the context is in sync with the editor state again.
7525 if (this.pendingContextChange) {
7526 this.revertPending(view.state);
7527 this.setSelection(view.state);
7528 }
7529 // Work around missed compositionend events. See https://discuss.codemirror.net/t/a/9514
7530 if (change.from < change.to && !change.insert.length && view.inputState.composing >= 0 &&
7531 !/[\\p{Alphabetic}\\p{Number}_]/.test(context.text.slice(Math.max(0, e.updateRangeStart - 1), Math.min(context.text.length, e.updateRangeStart + 1))))
7532 this.handlers.compositionend(e);
7533 };
7534 this.handlers.characterboundsupdate = e => {
7535 let rects = [], prev = null;
7536 for (let i = this.toEditorPos(e.rangeStart), end = this.toEditorPos(e.rangeEnd); i < end; i++) {
7537 let rect = view.coordsForChar(i);
7538 prev = (rect && new DOMRect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top))
7539 || prev || new DOMRect;
7540 rects.push(prev);
7541 }
7542 context.updateCharacterBounds(e.rangeStart, rects);
7543 };
7544 this.handlers.textformatupdate = e => {
7545 let deco = [];
7546 for (let format of e.getTextFormats()) {
7547 let lineStyle = format.underlineStyle, thickness = format.underlineThickness;
7548 if (!/none/i.test(lineStyle) && !/none/i.test(thickness)) {
7549 let from = this.toEditorPos(format.rangeStart), to = this.toEditorPos(format.rangeEnd);
7550 if (from < to) {
7551 // These values changed from capitalized custom strings to lower-case CSS keywords in 2025
7552 let style = `text-decoration: underline ${/^[a-z]/.test(lineStyle) ? lineStyle + " " : lineStyle == "Dashed" ? "dashed " : lineStyle == "Squiggle" ? "wavy " : ""}${/thin/i.test(thickness) ? 1 : 2}px`;
7553 deco.push(Decoration.mark({ attributes: { style } }).range(from, to));
7554 }
7555 }
7556 }
7557 view.dispatch({ effects: setEditContextFormatting.of(Decoration.set(deco)) });
7558 };
7559 this.handlers.compositionstart = () => {
7560 if (view.inputState.composing < 0) {
7561 view.inputState.composing = 0;
7562 view.inputState.compositionFirstChange = true;
7563 }
7564 };
7565 this.handlers.compositionend = () => {
7566 view.inputState.composing = -1;
7567 view.inputState.compositionFirstChange = null;
7568 if (this.composing) {
7569 let { drifted } = this.composing;
7570 this.composing = null;
7571 if (drifted)
7572 this.reset(view.state);
7573 }
7574 };
7575 for (let event in this.handlers)
7576 context.addEventListener(event, this.handlers[event]);
7577 this.measureReq = { read: view => {
7578 this.editContext.updateControlBounds(view.contentDOM.getBoundingClientRect());
7579 let sel = getSelection(view.root);
7580 if (sel && sel.rangeCount)
7581 this.editContext.updateSelectionBounds(sel.getRangeAt(0).getBoundingClientRect());
7582 } };
7583 }
7584 applyEdits(update) {
7585 let off = 0, abort = false, pending = this.pendingContextChange;
7586 update.changes.iterChanges((fromA, toA, _fromB, _toB, insert) => {
7587 if (abort)
7588 return;
7589 let dLen = insert.length - (toA - fromA);
7590 if (pending && toA >= pending.to) {
7591 if (pending.from == fromA && pending.to == toA && pending.insert.eq(insert)) {
7592 pending = this.pendingContextChange = null; // Match
7593 off += dLen;
7594 this.to += dLen;
7595 return;
7596 }
7597 else { // Mismatch, revert
7598 pending = null;
7599 this.revertPending(update.state);
7600 }
7601 }
7602 fromA += off;
7603 toA += off;
7604 if (toA <= this.from) { // Before the window
7605 this.from += dLen;
7606 this.to += dLen;
7607 }
7608 else if (fromA < this.to) { // Overlaps with window
7609 if (fromA < this.from || toA > this.to || (this.to - this.from) + insert.length > 30000 /* CxVp.MaxSize */) {
7610 abort = true;
7611 return;
7612 }
7613 this.editContext.updateText(this.toContextPos(fromA), this.toContextPos(toA), insert.toString());
7614 this.to += dLen;
7615 }
7616 off += dLen;
7617 });
7618 if (pending && !abort)
7619 this.revertPending(update.state);
7620 return !abort;
7621 }
7622 update(update) {
7623 let reverted = this.pendingContextChange, startSel = update.startState.selection.main;
7624 if (this.composing &&
7625 (this.composing.drifted ||
7626 (!update.changes.touchesRange(startSel.from, startSel.to) &&
7627 update.transactions.some(tr => !tr.isUserEvent("input.type") && tr.changes.touchesRange(this.from, this.to))))) {
7628 this.composing.drifted = true;
7629 this.composing.editorBase = update.changes.mapPos(this.composing.editorBase);
7630 }
7631 else if (!this.applyEdits(update) || !this.rangeIsValid(update.state)) {
7632 this.pendingContextChange = null;
7633 this.reset(update.state);
7634 }
7635 else if (update.docChanged || update.selectionSet || reverted) {
7636 this.setSelection(update.state);
7637 }
7638 if (update.geometryChanged || update.docChanged || update.selectionSet)
7639 update.view.requestMeasure(this.measureReq);
7640 }
7641 resetRange(state) {
7642 let { head } = state.selection.main;
7643 this.from = Math.max(0, head - 10000 /* CxVp.Margin */);
7644 this.to = Math.min(state.doc.length, head + 10000 /* CxVp.Margin */);
7645 }
7646 reset(state) {
7647 this.resetRange(state);
7648 this.editContext.updateText(0, this.editContext.text.length, state.doc.sliceString(this.from, this.to));
7649 this.setSelection(state);
7650 }
7651 revertPending(state) {
7652 let pending = this.pendingContextChange;
7653 this.pendingContextChange = null;
7654 this.editContext.updateText(this.toContextPos(pending.from), this.toContextPos(pending.from + pending.insert.length), state.doc.sliceString(pending.from, pending.to));
7655 }
7656 setSelection(state) {
7657 let { main } = state.selection;
7658 let start = this.toContextPos(Math.max(this.from, Math.min(this.to, main.anchor)));
7659 let end = this.toContextPos(main.head);
7660 if (this.editContext.selectionStart != start || this.editContext.selectionEnd != end)
7661 this.editContext.updateSelection(start, end);
7662 }
7663 rangeIsValid(state) {
7664 let { head } = state.selection.main;
7665 return !(this.from > 0 && head - this.from < 500 /* CxVp.MinMargin */ ||
7666 this.to < state.doc.length && this.to - head < 500 /* CxVp.MinMargin */ ||
7667 this.to - this.from > 10000 /* CxVp.Margin */ * 3);
7668 }
7669 toEditorPos(contextPos, clipLen = this.to - this.from) {
7670 contextPos = Math.min(contextPos, clipLen);
7671 let c = this.composing;
7672 return c && c.drifted ? c.editorBase + (contextPos - c.contextBase) : contextPos + this.from;
7673 }
7674 toContextPos(editorPos) {
7675 let c = this.composing;
7676 return c && c.drifted ? c.contextBase + (editorPos - c.editorBase) : editorPos - this.from;
7677 }
7678 destroy() {
7679 for (let event in this.handlers)
7680 this.editContext.removeEventListener(event, this.handlers[event]);
7681 }
7682}
7683
7684// The editor's update state machine looks something like this:
7685//
7686// Idle → Updating ⇆ Idle (unchecked) → Measuring → Idle
7687// ↑ ↓
7688// Updating (measure)
7689//
7690// The difference between 'Idle' and 'Idle (unchecked)' lies in
7691// whether a layout check has been scheduled. A regular update through
7692// the `update` method updates the DOM in a write-only fashion, and
7693// relies on a check (scheduled with `requestAnimationFrame`) to make
7694// sure everything is where it should be and the viewport covers the
7695// visible code. That check continues to measure and then optionally
7696// update until it reaches a coherent state.
7697/**
7698An editor view represents the editor's user interface. It holds
7699the editable DOM surface, and possibly other elements such as the
7700line number gutter. It handles events and dispatches state
7701transactions for editing actions.
7702*/
7703class EditorView {
7704 /**
7705 The current editor state.
7706 */
7707 get state() { return this.viewState.state; }
7708 /**
7709 To be able to display large documents without consuming too much
7710 memory or overloading the browser, CodeMirror only draws the
7711 code that is visible (plus a margin around it) to the DOM. This
7712 property tells you the extent of the current drawn viewport, in
7713 document positions.
7714 */
7715 get viewport() { return this.viewState.viewport; }
7716 /**
7717 When there are, for example, large collapsed ranges in the
7718 viewport, its size can be a lot bigger than the actual visible
7719 content. Thus, if you are doing something like styling the
7720 content in the viewport, it is preferable to only do so for
7721 these ranges, which are the subset of the viewport that is
7722 actually drawn.
7723 */
7724 get visibleRanges() { return this.viewState.visibleRanges; }
7725 /**
7726 Returns false when the editor is entirely scrolled out of view
7727 or otherwise hidden.
7728 */
7729 get inView() { return this.viewState.inView; }
7730 /**
7731 Indicates whether the user is currently composing text via
7732 [IME](https://en.wikipedia.org/wiki/Input_method), and at least
7733 one change has been made in the current composition.
7734 */
7735 get composing() { return !!this.inputState && this.inputState.composing > 0; }
7736 /**
7737 Indicates whether the user is currently in composing state. Note
7738 that on some platforms, like Android, this will be the case a
7739 lot, since just putting the cursor on a word starts a
7740 composition there.
7741 */
7742 get compositionStarted() { return !!this.inputState && this.inputState.composing >= 0; }
7743 /**
7744 The document or shadow root that the view lives in.
7745 */
7746 get root() { return this._root; }
7747 /**
7748 @internal
7749 */
7750 get win() { return this.dom.ownerDocument.defaultView || window; }
7751 /**
7752 Construct a new view. You'll want to either provide a `parent`
7753 option, or put `view.dom` into your document after creating a
7754 view, so that the user can see the editor.
7755 */
7756 constructor(config = {}) {
7757 var _a;
7758 this.plugins = [];
7759 this.pluginMap = new Map;
7760 this.editorAttrs = {};
7761 this.contentAttrs = {};
7762 this.bidiCache = [];
7763 this.destroyed = false;
7764 /**
7765 @internal
7766 */
7767 this.updateState = 2 /* UpdateState.Updating */;
7768 /**
7769 @internal
7770 */
7771 this.measureScheduled = -1;
7772 /**
7773 @internal
7774 */
7775 this.measureRequests = [];
7776 this.contentDOM = document.createElement("div");
7777 this.scrollDOM = document.createElement("div");
7778 this.scrollDOM.tabIndex = -1;
7779 this.scrollDOM.className = "cm-scroller";
7780 this.scrollDOM.appendChild(this.contentDOM);
7781 this.announceDOM = document.createElement("div");
7782 this.announceDOM.className = "cm-announced";
7783 this.announceDOM.setAttribute("aria-live", "polite");
7784 this.dom = document.createElement("div");
7785 this.dom.appendChild(this.announceDOM);
7786 this.dom.appendChild(this.scrollDOM);
7787 if (config.parent)
7788 config.parent.appendChild(this.dom);
7789 let { dispatch } = config;
7790 this.dispatchTransactions = config.dispatchTransactions ||
7791 (dispatch && ((trs) => trs.forEach(tr => dispatch(tr, this)))) ||
7792 ((trs) => this.update(trs));
7793 this.dispatch = this.dispatch.bind(this);
7794 this._root = (config.root || getRoot(config.parent) || document);
7795 this.viewState = new ViewState(this, config.state || state.EditorState.create(config));
7796 if (config.scrollTo && config.scrollTo.is(scrollIntoView))
7797 this.viewState.scrollTarget = config.scrollTo.value.clip(this.viewState.state);
7798 this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec));
7799 for (let plugin of this.plugins)
7800 plugin.update(this);
7801 this.observer = new DOMObserver(this);
7802 this.inputState = new InputState(this);
7803 this.inputState.ensureHandlers(this.plugins);
7804 this.docView = new DocView(this);
7805 this.mountStyles();
7806 this.updateAttrs();
7807 this.updateState = 0 /* UpdateState.Idle */;
7808 this.requestMeasure();
7809 if ((_a = document.fonts) === null || _a === void 0 ? void 0 : _a.ready)
7810 document.fonts.ready.then(() => {
7811 this.viewState.mustMeasureContent = "refresh";
7812 this.requestMeasure();
7813 });
7814 }
7815 dispatch(...input) {
7816 let trs = input.length == 1 && input[0] instanceof state.Transaction ? input
7817 : input.length == 1 && Array.isArray(input[0]) ? input[0]
7818 : [this.state.update(...input)];
7819 this.dispatchTransactions(trs, this);
7820 }
7821 /**
7822 Update the view for the given array of transactions. This will
7823 update the visible document and selection to match the state
7824 produced by the transactions, and notify view plugins of the
7825 change. You should usually call
7826 [`dispatch`](https://codemirror.net/6/docs/ref/#view.EditorView.dispatch) instead, which uses this
7827 as a primitive.
7828 */
7829 update(transactions) {
7830 if (this.updateState != 0 /* UpdateState.Idle */)
7831 throw new Error("Calls to EditorView.update are not allowed while an update is in progress");
7832 let redrawn = false, attrsChanged = false, update;
7833 let state$1 = this.state;
7834 for (let tr of transactions) {
7835 if (tr.startState != state$1)
7836 throw new RangeError("Trying to update state with a transaction that doesn't start from the previous state.");
7837 state$1 = tr.state;
7838 }
7839 if (this.destroyed) {
7840 this.viewState.state = state$1;
7841 return;
7842 }
7843 let focus = this.hasFocus, focusFlag = 0, dispatchFocus = null;
7844 if (transactions.some(tr => tr.annotation(isFocusChange))) {
7845 this.inputState.notifiedFocused = focus;
7846 // If a focus-change transaction is being dispatched, set this update flag.
7847 focusFlag = 1 /* UpdateFlag.Focus */;
7848 }
7849 else if (focus != this.inputState.notifiedFocused) {
7850 this.inputState.notifiedFocused = focus;
7851 // Schedule a separate focus transaction if necessary, otherwise
7852 // add a flag to this update
7853 dispatchFocus = focusChangeTransaction(state$1, focus);
7854 if (!dispatchFocus)
7855 focusFlag = 1 /* UpdateFlag.Focus */;
7856 }
7857 // If there was a pending DOM change, eagerly read it and try to
7858 // apply it after the given transactions.
7859 let pendingKey = this.observer.delayedAndroidKey, domChange = null;
7860 if (pendingKey) {
7861 this.observer.clearDelayedAndroidKey();
7862 domChange = this.observer.readChange();
7863 // Only try to apply DOM changes if the transactions didn't
7864 // change the doc or selection.
7865 if (domChange && !this.state.doc.eq(state$1.doc) || !this.state.selection.eq(state$1.selection))
7866 domChange = null;
7867 }
7868 else {
7869 this.observer.clear();
7870 }
7871 // When the phrases change, redraw the editor
7872 if (state$1.facet(state.EditorState.phrases) != this.state.facet(state.EditorState.phrases))
7873 return this.setState(state$1);
7874 update = ViewUpdate.create(this, state$1, transactions);
7875 update.flags |= focusFlag;
7876 let scrollTarget = this.viewState.scrollTarget;
7877 try {
7878 this.updateState = 2 /* UpdateState.Updating */;
7879 for (let tr of transactions) {
7880 if (scrollTarget)
7881 scrollTarget = scrollTarget.map(tr.changes);
7882 if (tr.scrollIntoView) {
7883 let { main } = tr.state.selection;
7884 scrollTarget = new ScrollTarget(main.empty ? main : state.EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1));
7885 }
7886 for (let e of tr.effects)
7887 if (e.is(scrollIntoView))
7888 scrollTarget = e.value.clip(this.state);
7889 }
7890 this.viewState.update(update, scrollTarget);
7891 this.bidiCache = CachedOrder.update(this.bidiCache, update.changes);
7892 if (!update.empty) {
7893 this.updatePlugins(update);
7894 this.inputState.update(update);
7895 }
7896 redrawn = this.docView.update(update);
7897 if (this.state.facet(styleModule) != this.styleModules)
7898 this.mountStyles();
7899 attrsChanged = this.updateAttrs();
7900 this.showAnnouncements(transactions);
7901 this.docView.updateSelection(redrawn, transactions.some(tr => tr.isUserEvent("select.pointer")));
7902 }
7903 finally {
7904 this.updateState = 0 /* UpdateState.Idle */;
7905 }
7906 if (update.startState.facet(theme) != update.state.facet(theme))
7907 this.viewState.mustMeasureContent = true;
7908 if (redrawn || attrsChanged || scrollTarget || this.viewState.mustEnforceCursorAssoc || this.viewState.mustMeasureContent)
7909 this.requestMeasure();
7910 if (redrawn)
7911 this.docViewUpdate();
7912 if (!update.empty)
7913 for (let listener of this.state.facet(updateListener)) {
7914 try {
7915 listener(update);
7916 }
7917 catch (e) {
7918 logException(this.state, e, "update listener");
7919 }
7920 }
7921 if (dispatchFocus || domChange)
7922 Promise.resolve().then(() => {
7923 if (dispatchFocus && this.state == dispatchFocus.startState)
7924 this.dispatch(dispatchFocus);
7925 if (domChange) {
7926 if (!applyDOMChange(this, domChange) && pendingKey.force)
7927 dispatchKey(this.contentDOM, pendingKey.key, pendingKey.keyCode);
7928 }
7929 });
7930 }
7931 /**
7932 Reset the view to the given state. (This will cause the entire
7933 document to be redrawn and all view plugins to be reinitialized,
7934 so you should probably only use it when the new state isn't
7935 derived from the old state. Otherwise, use
7936 [`dispatch`](https://codemirror.net/6/docs/ref/#view.EditorView.dispatch) instead.)
7937 */
7938 setState(newState) {
7939 if (this.updateState != 0 /* UpdateState.Idle */)
7940 throw new Error("Calls to EditorView.setState are not allowed while an update is in progress");
7941 if (this.destroyed) {
7942 this.viewState.state = newState;
7943 return;
7944 }
7945 this.updateState = 2 /* UpdateState.Updating */;
7946 let hadFocus = this.hasFocus;
7947 try {
7948 for (let plugin of this.plugins)
7949 plugin.destroy(this);
7950 this.viewState = new ViewState(this, newState);
7951 this.plugins = newState.facet(viewPlugin).map(spec => new PluginInstance(spec));
7952 this.pluginMap.clear();
7953 for (let plugin of this.plugins)
7954 plugin.update(this);
7955 this.docView.destroy();
7956 this.docView = new DocView(this);
7957 this.inputState.ensureHandlers(this.plugins);
7958 this.mountStyles();
7959 this.updateAttrs();
7960 this.bidiCache = [];
7961 }
7962 finally {
7963 this.updateState = 0 /* UpdateState.Idle */;
7964 }
7965 if (hadFocus)
7966 this.focus();
7967 this.requestMeasure();
7968 }
7969 updatePlugins(update) {
7970 let prevSpecs = update.startState.facet(viewPlugin), specs = update.state.facet(viewPlugin);
7971 if (prevSpecs != specs) {
7972 let newPlugins = [];
7973 for (let spec of specs) {
7974 let found = prevSpecs.indexOf(spec);
7975 if (found < 0) {
7976 newPlugins.push(new PluginInstance(spec));
7977 }
7978 else {
7979 let plugin = this.plugins[found];
7980 plugin.mustUpdate = update;
7981 newPlugins.push(plugin);
7982 }
7983 }
7984 for (let plugin of this.plugins)
7985 if (plugin.mustUpdate != update)
7986 plugin.destroy(this);
7987 this.plugins = newPlugins;
7988 this.pluginMap.clear();
7989 }
7990 else {
7991 for (let p of this.plugins)
7992 p.mustUpdate = update;
7993 }
7994 for (let i = 0; i < this.plugins.length; i++)
7995 this.plugins[i].update(this);
7996 if (prevSpecs != specs)
7997 this.inputState.ensureHandlers(this.plugins);
7998 }
7999 docViewUpdate() {
8000 for (let plugin of this.plugins) {
8001 let val = plugin.value;
8002 if (val && val.docViewUpdate) {
8003 try {
8004 val.docViewUpdate(this);
8005 }
8006 catch (e) {
8007 logException(this.state, e, "doc view update listener");
8008 }
8009 }
8010 }
8011 }
8012 /**
8013 @internal
8014 */
8015 measure(flush = true) {
8016 if (this.destroyed)
8017 return;
8018 if (this.measureScheduled > -1)
8019 this.win.cancelAnimationFrame(this.measureScheduled);
8020 if (this.observer.delayedAndroidKey) {
8021 this.measureScheduled = -1;
8022 this.requestMeasure();
8023 return;
8024 }
8025 this.measureScheduled = 0; // Prevent requestMeasure calls from scheduling another animation frame
8026 if (flush)
8027 this.observer.forceFlush();
8028 let updated = null;
8029 let scroll = this.viewState.scrollParent, scrollOffset = this.viewState.getScrollOffset();
8030 let { scrollAnchorPos, scrollAnchorHeight } = this.viewState;
8031 if (Math.abs(scrollOffset - this.viewState.scrollOffset) > 1)
8032 scrollAnchorHeight = -1;
8033 this.viewState.scrollAnchorHeight = -1;
8034 try {
8035 for (let i = 0;; i++) {
8036 if (scrollAnchorHeight < 0) {
8037 if (isScrolledToBottom(scroll || this.win)) {
8038 scrollAnchorPos = -1;
8039 scrollAnchorHeight = this.viewState.heightMap.height;
8040 }
8041 else {
8042 let block = this.viewState.scrollAnchorAt(scrollOffset);
8043 scrollAnchorPos = block.from;
8044 scrollAnchorHeight = block.top;
8045 }
8046 }
8047 this.updateState = 1 /* UpdateState.Measuring */;
8048 let changed = this.viewState.measure();
8049 if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
8050 break;
8051 if (i > 5) {
8052 console.warn(this.measureRequests.length
8053 ? "Measure loop restarted more than 5 times"
8054 : "Viewport failed to stabilize");
8055 break;
8056 }
8057 let measuring = [];
8058 // Only run measure requests in this cycle when the viewport didn't change
8059 if (!(changed & 4 /* UpdateFlag.Viewport */))
8060 [this.measureRequests, measuring] = [measuring, this.measureRequests];
8061 let measured = measuring.map(m => {
8062 try {
8063 return m.read(this);
8064 }
8065 catch (e) {
8066 logException(this.state, e);
8067 return BadMeasure;
8068 }
8069 });
8070 let update = ViewUpdate.create(this, this.state, []), redrawn = false;
8071 update.flags |= changed;
8072 if (!updated)
8073 updated = update;
8074 else
8075 updated.flags |= changed;
8076 this.updateState = 2 /* UpdateState.Updating */;
8077 if (!update.empty) {
8078 this.updatePlugins(update);
8079 this.inputState.update(update);
8080 this.updateAttrs();
8081 redrawn = this.docView.update(update);
8082 if (redrawn)
8083 this.docViewUpdate();
8084 }
8085 for (let i = 0; i < measuring.length; i++)
8086 if (measured[i] != BadMeasure) {
8087 try {
8088 let m = measuring[i];
8089 if (m.write)
8090 m.write(measured[i], this);
8091 }
8092 catch (e) {
8093 logException(this.state, e);
8094 }
8095 }
8096 if (redrawn)
8097 this.docView.updateSelection(true);
8098 if (!update.viewportChanged && this.measureRequests.length == 0) {
8099 if (this.viewState.editorHeight) {
8100 if (this.viewState.scrollTarget) {
8101 this.docView.scrollIntoView(this.viewState.scrollTarget);
8102 this.viewState.scrollTarget = null;
8103 scrollAnchorHeight = -1;
8104 continue;
8105 }
8106 else {
8107 let newAnchorHeight = scrollAnchorPos < 0 ? this.viewState.heightMap.height :
8108 this.viewState.lineBlockAt(scrollAnchorPos).top;
8109 let diff = (newAnchorHeight - scrollAnchorHeight) / this.scaleY;
8110 if ((diff > 1 || diff < -1) &&
8111 (scroll == this.scrollDOM || this.hasFocus ||
8112 Math.max(this.inputState.lastWheelEvent, this.inputState.lastTouchTime) > Date.now() - 100)) {
8113 scrollOffset = scrollOffset + diff;
8114 if (scroll)
8115 scroll.scrollTop += diff;
8116 else
8117 this.win.scrollBy(0, diff);
8118 scrollAnchorHeight = -1;
8119 continue;
8120 }
8121 }
8122 }
8123 break;
8124 }
8125 }
8126 }
8127 finally {
8128 this.updateState = 0 /* UpdateState.Idle */;
8129 this.measureScheduled = -1;
8130 }
8131 if (updated && !updated.empty)
8132 for (let listener of this.state.facet(updateListener))
8133 listener(updated);
8134 }
8135 /**
8136 Get the CSS classes for the currently active editor themes.
8137 */
8138 get themeClasses() {
8139 return baseThemeID + " " +
8140 (this.state.facet(darkTheme) ? baseDarkID : baseLightID) + " " +
8141 this.state.facet(theme);
8142 }
8143 updateAttrs() {
8144 let editorAttrs = attrsFromFacet(this, editorAttributes, {
8145 class: "cm-editor" + (this.hasFocus ? " cm-focused " : " ") + this.themeClasses
8146 });
8147 let contentAttrs = {
8148 spellcheck: "false",
8149 autocorrect: "off",
8150 autocapitalize: "off",
8151 writingsuggestions: "false",
8152 translate: "no",
8153 contenteditable: !this.state.facet(editable) ? "false" : "true",
8154 class: "cm-content",
8155 style: `${browser.tabSize}: ${this.state.tabSize}`,
8156 role: "textbox",
8157 "aria-multiline": "true"
8158 };
8159 if (this.state.readOnly)
8160 contentAttrs["aria-readonly"] = "true";
8161 attrsFromFacet(this, contentAttributes, contentAttrs);
8162 let changed = this.observer.ignore(() => {
8163 let changedContent = updateAttrs(this.contentDOM, this.contentAttrs, contentAttrs);
8164 let changedEditor = updateAttrs(this.dom, this.editorAttrs, editorAttrs);
8165 return changedContent || changedEditor;
8166 });
8167 this.editorAttrs = editorAttrs;
8168 this.contentAttrs = contentAttrs;
8169 return changed;
8170 }
8171 showAnnouncements(trs) {
8172 let first = true;
8173 for (let tr of trs)
8174 for (let effect of tr.effects)
8175 if (effect.is(EditorView.announce)) {
8176 if (first)
8177 this.announceDOM.textContent = "";
8178 first = false;
8179 let div = this.announceDOM.appendChild(document.createElement("div"));
8180 div.textContent = effect.value;
8181 }
8182 }
8183 mountStyles() {
8184 this.styleModules = this.state.facet(styleModule);
8185 let nonce = this.state.facet(EditorView.cspNonce);
8186 styleMod.StyleModule.mount(this.root, this.styleModules.concat(baseTheme$1).reverse(), nonce ? { nonce } : undefined);
8187 }
8188 readMeasured() {
8189 if (this.updateState == 2 /* UpdateState.Updating */)
8190 throw new Error("Reading the editor layout isn't allowed during an update");
8191 if (this.updateState == 0 /* UpdateState.Idle */ && this.measureScheduled > -1)
8192 this.measure(false);
8193 }
8194 /**
8195 Schedule a layout measurement, optionally providing callbacks to
8196 do custom DOM measuring followed by a DOM write phase. Using
8197 this is preferable reading DOM layout directly from, for
8198 example, an event handler, because it'll make sure measuring and
8199 drawing done by other components is synchronized, avoiding
8200 unnecessary DOM layout computations.
8201 */
8202 requestMeasure(request) {
8203 if (this.measureScheduled < 0)
8204 this.measureScheduled = this.win.requestAnimationFrame(() => this.measure());
8205 if (request) {
8206 if (this.measureRequests.indexOf(request) > -1)
8207 return;
8208 if (request.key != null)
8209 for (let i = 0; i < this.measureRequests.length; i++) {
8210 if (this.measureRequests[i].key === request.key) {
8211 this.measureRequests[i] = request;
8212 return;
8213 }
8214 }
8215 this.measureRequests.push(request);
8216 }
8217 }
8218 /**
8219 Get the value of a specific plugin, if present. Note that
8220 plugins that crash can be dropped from a view, so even when you
8221 know you registered a given plugin, it is recommended to check
8222 the return value of this method.
8223 */
8224 plugin(plugin) {
8225 let known = this.pluginMap.get(plugin);
8226 if (known === undefined || known && known.plugin != plugin)
8227 this.pluginMap.set(plugin, known = this.plugins.find(p => p.plugin == plugin) || null);
8228 return known && known.update(this).value;
8229 }
8230 /**
8231 The top position of the document, in screen coordinates. This
8232 may be negative when the editor is scrolled down. Points
8233 directly to the top of the first line, not above the padding.
8234 */
8235 get documentTop() {
8236 return this.contentDOM.getBoundingClientRect().top + this.viewState.paddingTop;
8237 }
8238 /**
8239 Reports the padding above and below the document.
8240 */
8241 get documentPadding() {
8242 return { top: this.viewState.paddingTop, bottom: this.viewState.paddingBottom };
8243 }
8244 /**
8245 If the editor is transformed with CSS, this provides the scale
8246 along the X axis. Otherwise, it will just be 1. Note that
8247 transforms other than translation and scaling are not supported.
8248 */
8249 get scaleX() { return this.viewState.scaleX; }
8250 /**
8251 Provide the CSS transformed scale along the Y axis.
8252 */
8253 get scaleY() { return this.viewState.scaleY; }
8254 /**
8255 Find the text line or block widget at the given vertical
8256 position (which is interpreted as relative to the [top of the
8257 document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop)).
8258 */
8259 elementAtHeight(height) {
8260 this.readMeasured();
8261 return this.viewState.elementAtHeight(height);
8262 }
8263 /**
8264 Find the line block (see
8265 [`lineBlockAt`](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt)) at the given
8266 height, again interpreted relative to the [top of the
8267 document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop).
8268 */
8269 lineBlockAtHeight(height) {
8270 this.readMeasured();
8271 return this.viewState.lineBlockAtHeight(height);
8272 }
8273 /**
8274 Get the extent and vertical position of all [line
8275 blocks](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt) in the viewport. Positions
8276 are relative to the [top of the
8277 document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop);
8278 */
8279 get viewportLineBlocks() {
8280 return this.viewState.viewportLines;
8281 }
8282 /**
8283 Find the line block around the given document position. A line
8284 block is a range delimited on both sides by either a
8285 non-[hidden](https://codemirror.net/6/docs/ref/#view.Decoration^replace) line break, or the
8286 start/end of the document. It will usually just hold a line of
8287 text, but may be broken into multiple textblocks by block
8288 widgets.
8289 */
8290 lineBlockAt(pos) {
8291 return this.viewState.lineBlockAt(pos);
8292 }
8293 /**
8294 The editor's total content height.
8295 */
8296 get contentHeight() {
8297 return this.viewState.contentHeight;
8298 }
8299 /**
8300 Move a cursor position by [grapheme
8301 cluster](https://codemirror.net/6/docs/ref/#state.findClusterBreak). `forward` determines whether
8302 the motion is away from the line start, or towards it. In
8303 bidirectional text, the line is traversed in visual order, using
8304 the editor's [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
8305 When the start position was the last one on the line, the
8306 returned position will be across the line break. If there is no
8307 further line, the original position is returned.
8308
8309 By default, this method moves over a single cluster. The
8310 optional `by` argument can be used to move across more. It will
8311 be called with the first cluster as argument, and should return
8312 a predicate that determines, for each subsequent cluster,
8313 whether it should also be moved over.
8314 */
8315 moveByChar(start, forward, by) {
8316 return skipAtoms(this, start, moveByChar(this, start, forward, by));
8317 }
8318 /**
8319 Move a cursor position across the next group of either
8320 [letters](https://codemirror.net/6/docs/ref/#state.EditorState.charCategorizer) or non-letter
8321 non-whitespace characters.
8322 */
8323 moveByGroup(start, forward) {
8324 return skipAtoms(this, start, moveByChar(this, start, forward, initial => byGroup(this, start.head, initial)));
8325 }
8326 /**
8327 Get the cursor position visually at the start or end of a line.
8328 Note that this may differ from the _logical_ position at its
8329 start or end (which is simply at `line.from`/`line.to`) if text
8330 at the start or end goes against the line's base text direction.
8331 */
8332 visualLineSide(line, end) {
8333 let order = this.bidiSpans(line), dir = this.textDirectionAt(line.from);
8334 let span = order[end ? order.length - 1 : 0];
8335 return state.EditorSelection.cursor(span.side(end, dir) + line.from, span.forward(!end, dir) ? 1 : -1);
8336 }
8337 /**
8338 Move to the next line boundary in the given direction. If
8339 `includeWrap` is true, line wrapping is on, and there is a
8340 further wrap point on the current line, the wrap point will be
8341 returned. Otherwise this function will return the start or end
8342 of the line.
8343 */
8344 moveToLineBoundary(start, forward, includeWrap = true) {
8345 return moveToLineBoundary(this, start, forward, includeWrap);
8346 }
8347 /**
8348 Move a cursor position vertically. When `distance` isn't given,
8349 it defaults to moving to the next line (including wrapped
8350 lines). Otherwise, `distance` should provide a positive distance
8351 in pixels.
8352
8353 When `start` has a
8354 [`goalColumn`](https://codemirror.net/6/docs/ref/#state.SelectionRange.goalColumn), the vertical
8355 motion will use that as a target horizontal position. Otherwise,
8356 the cursor's own horizontal position is used. The returned
8357 cursor will have its goal column set to whichever column was
8358 used.
8359 */
8360 moveVertically(start, forward, distance) {
8361 return skipAtoms(this, start, moveVertically(this, start, forward, distance));
8362 }
8363 /**
8364 Find the DOM parent node and offset (child offset if `node` is
8365 an element, character offset when it is a text node) at the
8366 given document position.
8367
8368 Note that for positions that aren't currently in
8369 `visibleRanges`, the resulting DOM position isn't necessarily
8370 meaningful (it may just point before or after a placeholder
8371 element).
8372 */
8373 domAtPos(pos, side = 1) {
8374 return this.docView.domAtPos(pos, side);
8375 }
8376 /**
8377 Find the document position at the given DOM node. Can be useful
8378 for associating positions with DOM events. Will raise an error
8379 when `node` isn't part of the editor content.
8380 */
8381 posAtDOM(node, offset = 0) {
8382 return this.docView.posFromDOM(node, offset);
8383 }
8384 posAtCoords(coords, precise = true) {
8385 this.readMeasured();
8386 let found = posAtCoords(this, coords, precise);
8387 return found && found.pos;
8388 }
8389 posAndSideAtCoords(coords, precise = true) {
8390 this.readMeasured();
8391 return posAtCoords(this, coords, precise);
8392 }
8393 /**
8394 Get the screen coordinates at the given document position.
8395 `side` determines whether the coordinates are based on the
8396 element before (-1) or after (1) the position (if no element is
8397 available on the given side, the method will transparently use
8398 another strategy to get reasonable coordinates).
8399 */
8400 coordsAtPos(pos, side = 1) {
8401 this.readMeasured();
8402 let rect = this.docView.coordsAt(pos, side);
8403 if (!rect || rect.left == rect.right)
8404 return rect;
8405 let line = this.state.doc.lineAt(pos), order = this.bidiSpans(line);
8406 let span = order[BidiSpan.find(order, pos - line.from, -1, side)];
8407 return flattenRect(rect, (span.dir == exports.Direction.LTR) == (side > 0));
8408 }
8409 /**
8410 Return the rectangle around a given character. If `pos` does not
8411 point in front of a character that is in the viewport and
8412 rendered (i.e. not replaced, not a line break), this will return
8413 null. For space characters that are a line wrap point, this will
8414 return the position before the line break.
8415 */
8416 coordsForChar(pos) {
8417 this.readMeasured();
8418 return this.docView.coordsForChar(pos);
8419 }
8420 /**
8421 The default width of a character in the editor. May not
8422 accurately reflect the width of all characters (given variable
8423 width fonts or styling of invididual ranges).
8424 */
8425 get defaultCharacterWidth() { return this.viewState.heightOracle.charWidth; }
8426 /**
8427 The default height of a line in the editor. May not be accurate
8428 for all lines.
8429 */
8430 get defaultLineHeight() { return this.viewState.heightOracle.lineHeight; }
8431 /**
8432 The text direction
8433 ([`direction`](https://developer.mozilla.org/en-US/docs/Web/CSS/direction)
8434 CSS property) of the editor's content element.
8435 */
8436 get textDirection() { return this.viewState.defaultTextDirection; }
8437 /**
8438 Find the text direction of the block at the given position, as
8439 assigned by CSS. If
8440 [`perLineTextDirection`](https://codemirror.net/6/docs/ref/#view.EditorView^perLineTextDirection)
8441 isn't enabled, or the given position is outside of the viewport,
8442 this will always return the same as
8443 [`textDirection`](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection). Note that
8444 this may trigger a DOM layout.
8445 */
8446 textDirectionAt(pos) {
8447 let perLine = this.state.facet(perLineTextDirection);
8448 if (!perLine || pos < this.viewport.from || pos > this.viewport.to)
8449 return this.textDirection;
8450 this.readMeasured();
8451 return this.docView.textDirectionAt(pos);
8452 }
8453 /**
8454 Whether this editor [wraps lines](https://codemirror.net/6/docs/ref/#view.EditorView.lineWrapping)
8455 (as determined by the
8456 [`white-space`](https://developer.mozilla.org/en-US/docs/Web/CSS/white-space)
8457 CSS property of its content element).
8458 */
8459 get lineWrapping() { return this.viewState.heightOracle.lineWrapping; }
8460 /**
8461 Returns the bidirectional text structure of the given line
8462 (which should be in the current document) as an array of span
8463 objects. The order of these spans matches the [text
8464 direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection)—if that is
8465 left-to-right, the leftmost spans come first, otherwise the
8466 rightmost spans come first.
8467 */
8468 bidiSpans(line) {
8469 if (line.length > MaxBidiLine)
8470 return trivialOrder(line.length);
8471 let dir = this.textDirectionAt(line.from), isolates;
8472 for (let entry of this.bidiCache) {
8473 if (entry.from == line.from && entry.dir == dir &&
8474 (entry.fresh || isolatesEq(entry.isolates, isolates = getIsolatedRanges(this, line))))
8475 return entry.order;
8476 }
8477 if (!isolates)
8478 isolates = getIsolatedRanges(this, line);
8479 let order = computeOrder(line.text, dir, isolates);
8480 this.bidiCache.push(new CachedOrder(line.from, line.to, dir, isolates, true, order));
8481 return order;
8482 }
8483 /**
8484 Check whether the editor has focus.
8485 */
8486 get hasFocus() {
8487 var _a;
8488 // Safari return false for hasFocus when the context menu is open
8489 // or closing, which leads us to ignore selection changes from the
8490 // context menu because it looks like the editor isn't focused.
8491 // This kludges around that.
8492 return (this.dom.ownerDocument.hasFocus() || browser.safari && ((_a = this.inputState) === null || _a === void 0 ? void 0 : _a.lastContextMenu) > Date.now() - 3e4) &&
8493 this.root.activeElement == this.contentDOM;
8494 }
8495 /**
8496 Put focus on the editor.
8497 */
8498 focus() {
8499 this.observer.ignore(() => {
8500 focusPreventScroll(this.contentDOM);
8501 this.docView.updateSelection();
8502 });
8503 }
8504 /**
8505 Update the [root](https://codemirror.net/6/docs/ref/##view.EditorViewConfig.root) in which the editor lives. This is only
8506 necessary when moving the editor's existing DOM to a new window or shadow root.
8507 */
8508 setRoot(root) {
8509 if (this._root != root) {
8510 this._root = root;
8511 this.observer.setWindow((root.nodeType == 9 ? root : root.ownerDocument).defaultView || window);
8512 this.mountStyles();
8513 }
8514 }
8515 /**
8516 Clean up this editor view, removing its element from the
8517 document, unregistering event handlers, and notifying
8518 plugins. The view instance can no longer be used after
8519 calling this.
8520 */
8521 destroy() {
8522 if (this.root.activeElement == this.contentDOM)
8523 this.contentDOM.blur();
8524 for (let plugin of this.plugins)
8525 plugin.destroy(this);
8526 this.plugins = [];
8527 this.inputState.destroy();
8528 this.docView.destroy();
8529 this.dom.remove();
8530 this.observer.destroy();
8531 if (this.measureScheduled > -1)
8532 this.win.cancelAnimationFrame(this.measureScheduled);
8533 this.destroyed = true;
8534 }
8535 /**
8536 Returns an effect that can be
8537 [added](https://codemirror.net/6/docs/ref/#state.TransactionSpec.effects) to a transaction to
8538 cause it to scroll the given position or range into view.
8539 */
8540 static scrollIntoView(pos, options = {}) {
8541 return scrollIntoView.of(new ScrollTarget(typeof pos == "number" ? state.EditorSelection.cursor(pos) : pos, options.y, options.x, options.yMargin, options.xMargin));
8542 }
8543 /**
8544 Return an effect that resets the editor to its current (at the
8545 time this method was called) scroll position. Note that this
8546 only affects the editor's own scrollable element, not parents.
8547 See also
8548 [`EditorViewConfig.scrollTo`](https://codemirror.net/6/docs/ref/#view.EditorViewConfig.scrollTo).
8549
8550 The effect should be used with a document identical to the one
8551 it was created for. Failing to do so is not an error, but may
8552 not scroll to the expected position. You can
8553 [map](https://codemirror.net/6/docs/ref/#state.StateEffect.map) the effect to account for changes.
8554 */
8555 scrollSnapshot() {
8556 let { scrollTop, scrollLeft } = this.scrollDOM;
8557 let ref = this.viewState.scrollAnchorAt(scrollTop);
8558 return scrollIntoView.of(new ScrollTarget(state.EditorSelection.cursor(ref.from), "start", "start", ref.top - scrollTop, scrollLeft, true));
8559 }
8560 /**
8561 Enable or disable tab-focus mode, which disables key bindings
8562 for Tab and Shift-Tab, letting the browser's default
8563 focus-changing behavior go through instead. This is useful to
8564 prevent trapping keyboard users in your editor.
8565
8566 Without argument, this toggles the mode. With a boolean, it
8567 enables (true) or disables it (false). Given a number, it
8568 temporarily enables the mode until that number of milliseconds
8569 have passed or another non-Tab key is pressed.
8570 */
8571 setTabFocusMode(to) {
8572 if (to == null)
8573 this.inputState.tabFocusMode = this.inputState.tabFocusMode < 0 ? 0 : -1;
8574 else if (typeof to == "boolean")
8575 this.inputState.tabFocusMode = to ? 0 : -1;
8576 else if (this.inputState.tabFocusMode != 0)
8577 this.inputState.tabFocusMode = Date.now() + to;
8578 }
8579 /**
8580 Returns an extension that can be used to add DOM event handlers.
8581 The value should be an object mapping event names to handler
8582 functions. For any given event, such functions are ordered by
8583 extension precedence, and the first handler to return true will
8584 be assumed to have handled that event, and no other handlers or
8585 built-in behavior will be activated for it. These are registered
8586 on the [content element](https://codemirror.net/6/docs/ref/#view.EditorView.contentDOM), except
8587 for `scroll` handlers, which will be called any time the
8588 editor's [scroll element](https://codemirror.net/6/docs/ref/#view.EditorView.scrollDOM) or one of
8589 its parent nodes is scrolled.
8590 */
8591 static domEventHandlers(handlers) {
8592 return ViewPlugin.define(() => ({}), { eventHandlers: handlers });
8593 }
8594 /**
8595 Create an extension that registers DOM event observers. Contrary
8596 to event [handlers](https://codemirror.net/6/docs/ref/#view.EditorView^domEventHandlers),
8597 observers can't be prevented from running by a higher-precedence
8598 handler returning true. They also don't prevent other handlers
8599 and observers from running when they return true, and should not
8600 call `preventDefault`.
8601 */
8602 static domEventObservers(observers) {
8603 return ViewPlugin.define(() => ({}), { eventObservers: observers });
8604 }
8605 /**
8606 Create a theme extension. The first argument can be a
8607 [`style-mod`](https://github.com/marijnh/style-mod#documentation)
8608 style spec providing the styles for the theme. These will be
8609 prefixed with a generated class for the style.
8610
8611 Because the selectors will be prefixed with a scope class, rule
8612 that directly match the editor's [wrapper
8613 element](https://codemirror.net/6/docs/ref/#view.EditorView.dom)—to which the scope class will be
8614 added—need to be explicitly differentiated by adding an `&` to
8615 the selector for that element—for example
8616 `&.cm-focused`.
8617
8618 When `dark` is set to true, the theme will be marked as dark,
8619 which will cause the `&dark` rules from [base
8620 themes](https://codemirror.net/6/docs/ref/#view.EditorView^baseTheme) to be used (as opposed to
8621 `&light` when a light theme is active).
8622 */
8623 static theme(spec, options) {
8624 let prefix = styleMod.StyleModule.newName();
8625 let result = [theme.of(prefix), styleModule.of(buildTheme(`.${prefix}`, spec))];
8626 if (options && options.dark)
8627 result.push(darkTheme.of(true));
8628 return result;
8629 }
8630 /**
8631 Create an extension that adds styles to the base theme. Like
8632 with [`theme`](https://codemirror.net/6/docs/ref/#view.EditorView^theme), use `&` to indicate the
8633 place of the editor wrapper element when directly targeting
8634 that. You can also use `&dark` or `&light` instead to only
8635 target editors with a dark or light theme.
8636 */
8637 static baseTheme(spec) {
8638 return state.Prec.lowest(styleModule.of(buildTheme("." + baseThemeID, spec, lightDarkIDs)));
8639 }
8640 /**
8641 Retrieve an editor view instance from the view's DOM
8642 representation.
8643 */
8644 static findFromDOM(dom) {
8645 var _a;
8646 let content = dom.querySelector(".cm-content");
8647 let tile = content && Tile.get(content) || Tile.get(dom);
8648 return ((_a = tile === null || tile === void 0 ? void 0 : tile.root) === null || _a === void 0 ? void 0 : _a.view) || null;
8649 }
8650}
8651/**
8652Facet to add a [style
8653module](https://github.com/marijnh/style-mod#documentation) to
8654an editor view. The view will ensure that the module is
8655mounted in its [document
8656root](https://codemirror.net/6/docs/ref/#view.EditorView.constructor^config.root).
8657*/
8658EditorView.styleModule = styleModule;
8659/**
8660An input handler can override the way changes to the editable
8661DOM content are handled. Handlers are passed the document
8662positions between which the change was found, and the new
8663content. When one returns true, no further input handlers are
8664called and the default behavior is prevented.
8665
8666The `insert` argument can be used to get the default transaction
8667that would be applied for this input. This can be useful when
8668dispatching the custom behavior as a separate transaction.
8669*/
8670EditorView.inputHandler = inputHandler;
8671/**
8672Functions provided in this facet will be used to transform text
8673pasted or dropped into the editor.
8674*/
8675EditorView.clipboardInputFilter = clipboardInputFilter;
8676/**
8677Transform text copied or dragged from the editor.
8678*/
8679EditorView.clipboardOutputFilter = clipboardOutputFilter;
8680/**
8681Scroll handlers can override how things are scrolled into view.
8682If they return `true`, no further handling happens for the
8683scrolling. If they return false, the default scroll behavior is
8684applied. Scroll handlers should never initiate editor updates.
8685*/
8686EditorView.scrollHandler = scrollHandler;
8687/**
8688This facet can be used to provide functions that create effects
8689to be dispatched when the editor's focus state changes.
8690*/
8691EditorView.focusChangeEffect = focusChangeEffect;
8692/**
8693By default, the editor assumes all its content has the same
8694[text direction](https://codemirror.net/6/docs/ref/#view.Direction). Configure this with a `true`
8695value to make it read the text direction of every (rendered)
8696line separately.
8697*/
8698EditorView.perLineTextDirection = perLineTextDirection;
8699/**
8700Allows you to provide a function that should be called when the
8701library catches an exception from an extension (mostly from view
8702plugins, but may be used by other extensions to route exceptions
8703from user-code-provided callbacks). This is mostly useful for
8704debugging and logging. See [`logException`](https://codemirror.net/6/docs/ref/#view.logException).
8705*/
8706EditorView.exceptionSink = exceptionSink;
8707/**
8708A facet that can be used to register a function to be called
8709every time the view updates.
8710*/
8711EditorView.updateListener = updateListener;
8712/**
8713Facet that controls whether the editor content DOM is editable.
8714When its highest-precedence value is `false`, the element will
8715not have its `contenteditable` attribute set. (Note that this
8716doesn't affect API calls that change the editor content, even
8717when those are bound to keys or buttons. See the
8718[`readOnly`](https://codemirror.net/6/docs/ref/#state.EditorState.readOnly) facet for that.)
8719*/
8720EditorView.editable = editable;
8721/**
8722Allows you to influence the way mouse selection happens. The
8723functions in this facet will be called for a `mousedown` event
8724on the editor, and can return an object that overrides the way a
8725selection is computed from that mouse click or drag.
8726*/
8727EditorView.mouseSelectionStyle = mouseSelectionStyle;
8728/**
8729Facet used to configure whether a given selection drag event
8730should move or copy the selection. The given predicate will be
8731called with the `mousedown` event, and can return `true` when
8732the drag should move the content.
8733*/
8734EditorView.dragMovesSelection = dragMovesSelection$1;
8735/**
8736Facet used to configure whether a given selecting click adds a
8737new range to the existing selection or replaces it entirely. The
8738default behavior is to check `event.metaKey` on macOS, and
8739`event.ctrlKey` elsewhere.
8740*/
8741EditorView.clickAddsSelectionRange = clickAddsSelectionRange;
8742/**
8743A facet that determines which [decorations](https://codemirror.net/6/docs/ref/#view.Decoration)
8744are shown in the view. Decorations can be provided in two
8745ways—directly, or via a function that takes an editor view.
8746
8747Only decoration sets provided directly are allowed to influence
8748the editor's vertical layout structure. The ones provided as
8749functions are called _after_ the new viewport has been computed,
8750and thus **must not** introduce block widgets or replacing
8751decorations that cover line breaks.
8752
8753If you want decorated ranges to behave like atomic units for
8754cursor motion and deletion purposes, also provide the range set
8755containing the decorations to
8756[`EditorView.atomicRanges`](https://codemirror.net/6/docs/ref/#view.EditorView^atomicRanges).
8757*/
8758EditorView.decorations = decorations;
8759/**
8760[Block wrappers](https://codemirror.net/6/docs/ref/#view.BlockWrapper) provide a way to add DOM
8761structure around editor lines and block widgets. Sets of
8762wrappers are provided in a similar way to decorations, and are
8763nested in a similar way when they overlap. A wrapper affects all
8764lines and block widgets that start inside its range.
8765*/
8766EditorView.blockWrappers = blockWrappers;
8767/**
8768Facet that works much like
8769[`decorations`](https://codemirror.net/6/docs/ref/#view.EditorView^decorations), but puts its
8770inputs at the very bottom of the precedence stack, meaning mark
8771decorations provided here will only be split by other, partially
8772overlapping `outerDecorations` ranges, and wrap around all
8773regular decorations. Use this for mark elements that should, as
8774much as possible, remain in one piece.
8775*/
8776EditorView.outerDecorations = outerDecorations;
8777/**
8778Used to provide ranges that should be treated as atoms as far as
8779cursor motion is concerned. This causes methods like
8780[`moveByChar`](https://codemirror.net/6/docs/ref/#view.EditorView.moveByChar) and
8781[`moveVertically`](https://codemirror.net/6/docs/ref/#view.EditorView.moveVertically) (and the
8782commands built on top of them) to skip across such regions when
8783a selection endpoint would enter them. This does _not_ prevent
8784direct programmatic [selection
8785updates](https://codemirror.net/6/docs/ref/#state.TransactionSpec.selection) from moving into such
8786regions.
8787*/
8788EditorView.atomicRanges = atomicRanges;
8789/**
8790When range decorations add a `unicode-bidi: isolate` style, they
8791should also include a
8792[`bidiIsolate`](https://codemirror.net/6/docs/ref/#view.MarkDecorationSpec.bidiIsolate) property
8793in their decoration spec, and be exposed through this facet, so
8794that the editor can compute the proper text order. (Other values
8795for `unicode-bidi`, except of course `normal`, are not
8796supported.)
8797*/
8798EditorView.bidiIsolatedRanges = bidiIsolatedRanges;
8799/**
8800Facet that allows extensions to provide additional scroll
8801margins (space around the sides of the scrolling element that
8802should be considered invisible). This can be useful when the
8803plugin introduces elements that cover part of that element (for
8804example a horizontally fixed gutter).
8805*/
8806EditorView.scrollMargins = scrollMargins;
8807/**
8808This facet records whether a dark theme is active. The extension
8809returned by [`theme`](https://codemirror.net/6/docs/ref/#view.EditorView^theme) automatically
8810includes an instance of this when the `dark` option is set to
8811true.
8812*/
8813EditorView.darkTheme = darkTheme;
8814/**
8815Provides a Content Security Policy nonce to use when creating
8816the style sheets for the editor. Holds the empty string when no
8817nonce has been provided.
8818*/
8819EditorView.cspNonce = state.Facet.define({ combine: values => values.length ? values[0] : "" });
8820/**
8821Facet that provides additional DOM attributes for the editor's
8822editable DOM element.
8823*/
8824EditorView.contentAttributes = contentAttributes;
8825/**
8826Facet that provides DOM attributes for the editor's outer
8827element.
8828*/
8829EditorView.editorAttributes = editorAttributes;
8830/**
8831An extension that enables line wrapping in the editor (by
8832setting CSS `white-space` to `pre-wrap` in the content).
8833*/
8834EditorView.lineWrapping = EditorView.contentAttributes.of({ "class": "cm-lineWrapping" });
8835/**
8836State effect used to include screen reader announcements in a
8837transaction. These will be added to the DOM in a visually hidden
8838element with `aria-live="polite"` set, and should be used to
8839describe effects that are visually obvious but may not be
8840noticed by screen reader users (such as moving to the next
8841search match).
8842*/
8843EditorView.announce = state.StateEffect.define();
8844// Maximum line length for which we compute accurate bidi info
8845const MaxBidiLine = 4096;
8846const BadMeasure = {};
8847class CachedOrder {
8848 constructor(from, to, dir, isolates, fresh, order) {
8849 this.from = from;
8850 this.to = to;
8851 this.dir = dir;
8852 this.isolates = isolates;
8853 this.fresh = fresh;
8854 this.order = order;
8855 }
8856 static update(cache, changes) {
8857 if (changes.empty && !cache.some(c => c.fresh))
8858 return cache;
8859 let result = [], lastDir = cache.length ? cache[cache.length - 1].dir : exports.Direction.LTR;
8860 for (let i = Math.max(0, cache.length - 10); i < cache.length; i++) {
8861 let entry = cache[i];
8862 if (entry.dir == lastDir && !changes.touchesRange(entry.from, entry.to))
8863 result.push(new CachedOrder(changes.mapPos(entry.from, 1), changes.mapPos(entry.to, -1), entry.dir, entry.isolates, false, entry.order));
8864 }
8865 return result;
8866 }
8867}
8868function attrsFromFacet(view, facet, base) {
8869 for (let sources = view.state.facet(facet), i = sources.length - 1; i >= 0; i--) {
8870 let source = sources[i], value = typeof source == "function" ? source(view) : source;
8871 if (value)
8872 combineAttrs(value, base);
8873 }
8874 return base;
8875}
8876
8877const currentPlatform = browser.mac ? "mac" : browser.windows ? "win" : browser.linux ? "linux" : "key";
8878function normalizeKeyName(name, platform) {
8879 const parts = name.split(/-(?!$)/);
8880 let result = parts[parts.length - 1];
8881 if (result == "Space")
8882 result = " ";
8883 let alt, ctrl, shift, meta;
8884 for (let i = 0; i < parts.length - 1; ++i) {
8885 const mod = parts[i];
8886 if (/^(cmd|meta|m)$/i.test(mod))
8887 meta = true;
8888 else if (/^a(lt)?$/i.test(mod))
8889 alt = true;
8890 else if (/^(c|ctrl|control)$/i.test(mod))
8891 ctrl = true;
8892 else if (/^s(hift)?$/i.test(mod))
8893 shift = true;
8894 else if (/^mod$/i.test(mod)) {
8895 if (platform == "mac")
8896 meta = true;
8897 else
8898 ctrl = true;
8899 }
8900 else
8901 throw new Error("Unrecognized modifier name: " + mod);
8902 }
8903 if (alt)
8904 result = "Alt-" + result;
8905 if (ctrl)
8906 result = "Ctrl-" + result;
8907 if (meta)
8908 result = "Meta-" + result;
8909 if (shift)
8910 result = "Shift-" + result;
8911 return result;
8912}
8913function modifiers(name, event, shift) {
8914 if (event.altKey)
8915 name = "Alt-" + name;
8916 if (event.ctrlKey)
8917 name = "Ctrl-" + name;
8918 if (event.metaKey)
8919 name = "Meta-" + name;
8920 if (shift !== false && event.shiftKey)
8921 name = "Shift-" + name;
8922 return name;
8923}
8924const handleKeyEvents = state.Prec.default(EditorView.domEventHandlers({
8925 keydown(event, view) {
8926 return runHandlers(getKeymap(view.state), event, view, "editor");
8927 }
8928}));
8929/**
8930Facet used for registering keymaps.
8931
8932You can add multiple keymaps to an editor. Their priorities
8933determine their precedence (the ones specified early or with high
8934priority get checked first). When a handler has returned `true`
8935for a given key, no further handlers are called.
8936*/
8937const keymap = state.Facet.define({ enables: handleKeyEvents });
8938const Keymaps = new WeakMap();
8939// This is hidden behind an indirection, rather than directly computed
8940// by the facet, to keep internal types out of the facet's type.
8941function getKeymap(state) {
8942 let bindings = state.facet(keymap);
8943 let map = Keymaps.get(bindings);
8944 if (!map)
8945 Keymaps.set(bindings, map = buildKeymap(bindings.reduce((a, b) => a.concat(b), [])));
8946 return map;
8947}
8948/**
8949Run the key handlers registered for a given scope. The event
8950object should be a `"keydown"` event. Returns true if any of the
8951handlers handled it.
8952*/
8953function runScopeHandlers(view, event, scope) {
8954 return runHandlers(getKeymap(view.state), event, view, scope);
8955}
8956let storedPrefix = null;
8957const PrefixTimeout = 4000;
8958function buildKeymap(bindings, platform = currentPlatform) {
8959 let bound = Object.create(null);
8960 let isPrefix = Object.create(null);
8961 let checkPrefix = (name, is) => {
8962 let current = isPrefix[name];
8963 if (current == null)
8964 isPrefix[name] = is;
8965 else if (current != is)
8966 throw new Error("Key binding " + name + " is used both as a regular binding and as a multi-stroke prefix");
8967 };
8968 let add = (scope, key, command, preventDefault, stopPropagation) => {
8969 var _a, _b;
8970 let scopeObj = bound[scope] || (bound[scope] = Object.create(null));
8971 let parts = key.split(/ (?!$)/).map(k => normalizeKeyName(k, platform));
8972 for (let i = 1; i < parts.length; i++) {
8973 let prefix = parts.slice(0, i).join(" ");
8974 checkPrefix(prefix, true);
8975 if (!scopeObj[prefix])
8976 scopeObj[prefix] = {
8977 preventDefault: true,
8978 stopPropagation: false,
8979 run: [(view) => {
8980 let ourObj = storedPrefix = { view, prefix, scope };
8981 setTimeout(() => { if (storedPrefix == ourObj)
8982 storedPrefix = null; }, PrefixTimeout);
8983 return true;
8984 }]
8985 };
8986 }
8987 let full = parts.join(" ");
8988 checkPrefix(full, false);
8989 let binding = scopeObj[full] || (scopeObj[full] = {
8990 preventDefault: false,
8991 stopPropagation: false,
8992 run: ((_b = (_a = scopeObj._any) === null || _a === void 0 ? void 0 : _a.run) === null || _b === void 0 ? void 0 : _b.slice()) || []
8993 });
8994 if (command)
8995 binding.run.push(command);
8996 if (preventDefault)
8997 binding.preventDefault = true;
8998 if (stopPropagation)
8999 binding.stopPropagation = true;
9000 };
9001 for (let b of bindings) {
9002 let scopes = b.scope ? b.scope.split(" ") : ["editor"];
9003 if (b.any)
9004 for (let scope of scopes) {
9005 let scopeObj = bound[scope] || (bound[scope] = Object.create(null));
9006 if (!scopeObj._any)
9007 scopeObj._any = { preventDefault: false, stopPropagation: false, run: [] };
9008 let { any } = b;
9009 for (let key in scopeObj)
9010 scopeObj[key].run.push(view => any(view, currentKeyEvent));
9011 }
9012 let name = b[platform] || b.key;
9013 if (!name)
9014 continue;
9015 for (let scope of scopes) {
9016 add(scope, name, b.run, b.preventDefault, b.stopPropagation);
9017 if (b.shift)
9018 add(scope, "Shift-" + name, b.shift, b.preventDefault, b.stopPropagation);
9019 }
9020 }
9021 return bound;
9022}
9023let currentKeyEvent = null;
9024function runHandlers(map, event, view, scope) {
9025 currentKeyEvent = event;
9026 let name = w3cKeyname.keyName(event);
9027 let charCode = state.codePointAt(name, 0), isChar = state.codePointSize(charCode) == name.length && name != " ";
9028 let prefix = "", handled = false, prevented = false, stopPropagation = false;
9029 if (storedPrefix && storedPrefix.view == view && storedPrefix.scope == scope) {
9030 prefix = storedPrefix.prefix + " ";
9031 if (modifierCodes.indexOf(event.keyCode) < 0) {
9032 prevented = true;
9033 storedPrefix = null;
9034 }
9035 }
9036 let ran = new Set;
9037 let runFor = (binding) => {
9038 if (binding) {
9039 for (let cmd of binding.run)
9040 if (!ran.has(cmd)) {
9041 ran.add(cmd);
9042 if (cmd(view)) {
9043 if (binding.stopPropagation)
9044 stopPropagation = true;
9045 return true;
9046 }
9047 }
9048 if (binding.preventDefault) {
9049 if (binding.stopPropagation)
9050 stopPropagation = true;
9051 prevented = true;
9052 }
9053 }
9054 return false;
9055 };
9056 let scopeObj = map[scope], baseName, shiftName;
9057 if (scopeObj) {
9058 if (runFor(scopeObj[prefix + modifiers(name, event, !isChar)])) {
9059 handled = true;
9060 }
9061 else if (isChar && (event.altKey || event.metaKey || event.ctrlKey) &&
9062 // Ctrl-Alt may be used for AltGr on Windows
9063 !(browser.windows && event.ctrlKey && event.altKey) &&
9064 // Alt-combinations on macOS tend to be typed characters
9065 !(browser.mac && event.altKey && !(event.ctrlKey || event.metaKey)) &&
9066 (baseName = w3cKeyname.base[event.keyCode]) && baseName != name) {
9067 if (runFor(scopeObj[prefix + modifiers(baseName, event, true)])) {
9068 handled = true;
9069 }
9070 else if (event.shiftKey && (shiftName = w3cKeyname.shift[event.keyCode]) != name && shiftName != baseName &&
9071 runFor(scopeObj[prefix + modifiers(shiftName, event, false)])) {
9072 handled = true;
9073 }
9074 }
9075 else if (isChar && event.shiftKey &&
9076 runFor(scopeObj[prefix + modifiers(name, event, true)])) {
9077 handled = true;
9078 }
9079 if (!handled && runFor(scopeObj._any))
9080 handled = true;
9081 }
9082 if (prevented)
9083 handled = true;
9084 if (handled && stopPropagation)
9085 event.stopPropagation();
9086 currentKeyEvent = null;
9087 return handled;
9088}
9089
9090/**
9091Implementation of [`LayerMarker`](https://codemirror.net/6/docs/ref/#view.LayerMarker) that creates
9092a rectangle at a given set of coordinates.
9093*/
9094class RectangleMarker {
9095 /**
9096 Create a marker with the given class and dimensions. If `width`
9097 is null, the DOM element will get no width style.
9098 */
9099 constructor(className,
9100 /**
9101 The left position of the marker (in pixels, document-relative).
9102 */
9103 left,
9104 /**
9105 The top position of the marker.
9106 */
9107 top,
9108 /**
9109 The width of the marker, or null if it shouldn't get a width assigned.
9110 */
9111 width,
9112 /**
9113 The height of the marker.
9114 */
9115 height) {
9116 this.className = className;
9117 this.left = left;
9118 this.top = top;
9119 this.width = width;
9120 this.height = height;
9121 }
9122 draw() {
9123 let elt = document.createElement("div");
9124 elt.className = this.className;
9125 this.adjust(elt);
9126 return elt;
9127 }
9128 update(elt, prev) {
9129 if (prev.className != this.className)
9130 return false;
9131 this.adjust(elt);
9132 return true;
9133 }
9134 adjust(elt) {
9135 elt.style.left = this.left + "px";
9136 elt.style.top = this.top + "px";
9137 if (this.width != null)
9138 elt.style.width = this.width + "px";
9139 elt.style.height = this.height + "px";
9140 }
9141 eq(p) {
9142 return this.left == p.left && this.top == p.top && this.width == p.width && this.height == p.height &&
9143 this.className == p.className;
9144 }
9145 /**
9146 Create a set of rectangles for the given selection range,
9147 assigning them theclass`className`. Will create a single
9148 rectangle for empty ranges, and a set of selection-style
9149 rectangles covering the range's content (in a bidi-aware
9150 way) for non-empty ones.
9151 */
9152 static forRange(view, className, range) {
9153 if (range.empty) {
9154 let pos = view.coordsAtPos(range.head, range.assoc || 1);
9155 if (!pos)
9156 return [];
9157 let base = getBase(view);
9158 return [new RectangleMarker(className, pos.left - base.left, pos.top - base.top, null, pos.bottom - pos.top)];
9159 }
9160 else {
9161 return rectanglesForRange(view, className, range);
9162 }
9163 }
9164}
9165function getBase(view) {
9166 let rect = view.scrollDOM.getBoundingClientRect();
9167 let left = view.textDirection == exports.Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth * view.scaleX;
9168 return { left: left - view.scrollDOM.scrollLeft * view.scaleX, top: rect.top - view.scrollDOM.scrollTop * view.scaleY };
9169}
9170function wrappedLine(view, pos, side, inside) {
9171 let coords = view.coordsAtPos(pos, side * 2);
9172 if (!coords)
9173 return inside;
9174 let editorRect = view.dom.getBoundingClientRect();
9175 let y = (coords.top + coords.bottom) / 2;
9176 let left = view.posAtCoords({ x: editorRect.left + 1, y });
9177 let right = view.posAtCoords({ x: editorRect.right - 1, y });
9178 if (left == null || right == null)
9179 return inside;
9180 return { from: Math.max(inside.from, Math.min(left, right)), to: Math.min(inside.to, Math.max(left, right)) };
9181}
9182function rectanglesForRange(view, className, range) {
9183 if (range.to <= view.viewport.from || range.from >= view.viewport.to)
9184 return [];
9185 let from = Math.max(range.from, view.viewport.from), to = Math.min(range.to, view.viewport.to);
9186 let ltr = view.textDirection == exports.Direction.LTR;
9187 let content = view.contentDOM, contentRect = content.getBoundingClientRect(), base = getBase(view);
9188 let lineElt = content.querySelector(".cm-line"), lineStyle = lineElt && window.getComputedStyle(lineElt);
9189 let leftSide = contentRect.left +
9190 (lineStyle ? parseInt(lineStyle.paddingLeft) + Math.min(0, parseInt(lineStyle.textIndent)) : 0);
9191 let rightSide = contentRect.right - (lineStyle ? parseInt(lineStyle.paddingRight) : 0);
9192 let startBlock = blockAt(view, from, 1), endBlock = blockAt(view, to, -1);
9193 let visualStart = startBlock.type == exports.BlockType.Text ? startBlock : null;
9194 let visualEnd = endBlock.type == exports.BlockType.Text ? endBlock : null;
9195 if (visualStart && (view.lineWrapping || startBlock.widgetLineBreaks))
9196 visualStart = wrappedLine(view, from, 1, visualStart);
9197 if (visualEnd && (view.lineWrapping || endBlock.widgetLineBreaks))
9198 visualEnd = wrappedLine(view, to, -1, visualEnd);
9199 if (visualStart && visualEnd && visualStart.from == visualEnd.from && visualStart.to == visualEnd.to) {
9200 return pieces(drawForLine(range.from, range.to, visualStart));
9201 }
9202 else {
9203 let top = visualStart ? drawForLine(range.from, null, visualStart) : drawForWidget(startBlock, false);
9204 let bottom = visualEnd ? drawForLine(null, range.to, visualEnd) : drawForWidget(endBlock, true);
9205 let between = [];
9206 if ((visualStart || startBlock).to < (visualEnd || endBlock).from - (visualStart && visualEnd ? 1 : 0) ||
9207 startBlock.widgetLineBreaks > 1 && top.bottom + view.defaultLineHeight / 2 < bottom.top)
9208 between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
9209 else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == exports.BlockType.Text)
9210 top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
9211 return pieces(top).concat(between).concat(pieces(bottom));
9212 }
9213 function piece(left, top, right, bottom) {
9214 return new RectangleMarker(className, left - base.left, top - base.top, Math.max(0, right - left), bottom - top);
9215 }
9216 function pieces({ top, bottom, horizontal }) {
9217 let pieces = [];
9218 for (let i = 0; i < horizontal.length; i += 2)
9219 pieces.push(piece(horizontal[i], top, horizontal[i + 1], bottom));
9220 return pieces;
9221 }
9222 // Gets passed from/to in line-local positions
9223 function drawForLine(from, to, line) {
9224 let top = 1e9, bottom = -1e9, horizontal = [];
9225 function addSpan(from, fromOpen, to, toOpen, dir) {
9226 // Passing 2/-2 is a kludge to force the view to return
9227 // coordinates on the proper side of block widgets, since
9228 // normalizing the side there, though appropriate for most
9229 // coordsAtPos queries, would break selection drawing.
9230 let fromCoords = view.coordsAtPos(from, (from == line.to ? -2 : 2));
9231 let toCoords = view.coordsAtPos(to, (to == line.from ? 2 : -2));
9232 if (!fromCoords || !toCoords)
9233 return;
9234 top = Math.min(fromCoords.top, toCoords.top, top);
9235 bottom = Math.max(fromCoords.bottom, toCoords.bottom, bottom);
9236 if (dir == exports.Direction.LTR)
9237 horizontal.push(ltr && fromOpen ? leftSide : fromCoords.left, ltr && toOpen ? rightSide : toCoords.right);
9238 else
9239 horizontal.push(!ltr && toOpen ? leftSide : toCoords.left, !ltr && fromOpen ? rightSide : fromCoords.right);
9240 }
9241 let start = from !== null && from !== void 0 ? from : line.from, end = to !== null && to !== void 0 ? to : line.to;
9242 // Split the range by visible range and document line
9243 for (let r of view.visibleRanges)
9244 if (r.to > start && r.from < end) {
9245 for (let pos = Math.max(r.from, start), endPos = Math.min(r.to, end);;) {
9246 let docLine = view.state.doc.lineAt(pos);
9247 for (let span of view.bidiSpans(docLine)) {
9248 let spanFrom = span.from + docLine.from, spanTo = span.to + docLine.from;
9249 if (spanFrom >= endPos)
9250 break;
9251 if (spanTo > pos)
9252 addSpan(Math.max(spanFrom, pos), from == null && spanFrom <= start, Math.min(spanTo, endPos), to == null && spanTo >= end, span.dir);
9253 }
9254 pos = docLine.to + 1;
9255 if (pos >= endPos)
9256 break;
9257 }
9258 }
9259 if (horizontal.length == 0)
9260 addSpan(start, from == null, end, to == null, view.textDirection);
9261 return { top, bottom, horizontal };
9262 }
9263 function drawForWidget(block, top) {
9264 let y = contentRect.top + (top ? block.top : block.bottom);
9265 return { top: y, bottom: y, horizontal: [] };
9266 }
9267}
9268function sameMarker(a, b) {
9269 return a.constructor == b.constructor && a.eq(b);
9270}
9271class LayerView {
9272 constructor(view, layer) {
9273 this.view = view;
9274 this.layer = layer;
9275 this.drawn = [];
9276 this.scaleX = 1;
9277 this.scaleY = 1;
9278 this.measureReq = { read: this.measure.bind(this), write: this.draw.bind(this) };
9279 this.dom = view.scrollDOM.appendChild(document.createElement("div"));
9280 this.dom.classList.add("cm-layer");
9281 if (layer.above)
9282 this.dom.classList.add("cm-layer-above");
9283 if (layer.class)
9284 this.dom.classList.add(layer.class);
9285 this.scale();
9286 this.dom.setAttribute("aria-hidden", "true");
9287 this.setOrder(view.state);
9288 view.requestMeasure(this.measureReq);
9289 if (layer.mount)
9290 layer.mount(this.dom, view);
9291 }
9292 update(update) {
9293 if (update.startState.facet(layerOrder) != update.state.facet(layerOrder))
9294 this.setOrder(update.state);
9295 if (this.layer.update(update, this.dom) || update.geometryChanged) {
9296 this.scale();
9297 update.view.requestMeasure(this.measureReq);
9298 }
9299 }
9300 docViewUpdate(view) {
9301 if (this.layer.updateOnDocViewUpdate !== false)
9302 view.requestMeasure(this.measureReq);
9303 }
9304 setOrder(state) {
9305 let pos = 0, order = state.facet(layerOrder);
9306 while (pos < order.length && order[pos] != this.layer)
9307 pos++;
9308 this.dom.style.zIndex = String((this.layer.above ? 150 : -1) - pos);
9309 }
9310 measure() {
9311 return this.layer.markers(this.view);
9312 }
9313 scale() {
9314 let { scaleX, scaleY } = this.view;
9315 if (scaleX != this.scaleX || scaleY != this.scaleY) {
9316 this.scaleX = scaleX;
9317 this.scaleY = scaleY;
9318 this.dom.style.transform = `scale(${1 / scaleX}, ${1 / scaleY})`;
9319 }
9320 }
9321 draw(markers) {
9322 if (markers.length != this.drawn.length || markers.some((p, i) => !sameMarker(p, this.drawn[i]))) {
9323 let old = this.dom.firstChild, oldI = 0;
9324 for (let marker of markers) {
9325 if (marker.update && old && marker.constructor && this.drawn[oldI].constructor &&
9326 marker.update(old, this.drawn[oldI])) {
9327 old = old.nextSibling;
9328 oldI++;
9329 }
9330 else {
9331 this.dom.insertBefore(marker.draw(), old);
9332 }
9333 }
9334 while (old) {
9335 let next = old.nextSibling;
9336 old.remove();
9337 old = next;
9338 }
9339 this.drawn = markers;
9340 if (browser.safari && browser.safari_version >= 26) // Issue #1600, 1627
9341 this.dom.style.display = this.dom.firstChild ? "" : "none";
9342 }
9343 }
9344 destroy() {
9345 if (this.layer.destroy)
9346 this.layer.destroy(this.dom, this.view);
9347 this.dom.remove();
9348 }
9349}
9350const layerOrder = state.Facet.define();
9351/**
9352Define a layer.
9353*/
9354function layer(config) {
9355 return [
9356 ViewPlugin.define(v => new LayerView(v, config)),
9357 layerOrder.of(config)
9358 ];
9359}
9360
9361const selectionConfig = state.Facet.define({
9362 combine(configs) {
9363 return state.combineConfig(configs, {
9364 cursorBlinkRate: 1200,
9365 drawRangeCursor: true
9366 }, {
9367 cursorBlinkRate: (a, b) => Math.min(a, b),
9368 drawRangeCursor: (a, b) => a || b
9369 });
9370 }
9371});
9372/**
9373Returns an extension that hides the browser's native selection and
9374cursor, replacing the selection with a background behind the text
9375(with the `cm-selectionBackground` class), and the
9376cursors with elements overlaid over the code (using
9377`cm-cursor-primary` and `cm-cursor-secondary`).
9378
9379This allows the editor to display secondary selection ranges, and
9380tends to produce a type of selection more in line with that users
9381expect in a text editor (the native selection styling will often
9382leave gaps between lines and won't fill the horizontal space after
9383a line when the selection continues past it).
9384
9385It does have a performance cost, in that it requires an extra DOM
9386layout cycle for many updates (the selection is drawn based on DOM
9387layout information that's only available after laying out the
9388content).
9389*/
9390function drawSelection(config = {}) {
9391 return [
9392 selectionConfig.of(config),
9393 cursorLayer,
9394 selectionLayer,
9395 hideNativeSelection,
9396 nativeSelectionHidden.of(true)
9397 ];
9398}
9399/**
9400Retrieve the [`drawSelection`](https://codemirror.net/6/docs/ref/#view.drawSelection) configuration
9401for this state. (Note that this will return a set of defaults even
9402if `drawSelection` isn't enabled.)
9403*/
9404function getDrawSelectionConfig(state) {
9405 return state.facet(selectionConfig);
9406}
9407function configChanged(update) {
9408 return update.startState.facet(selectionConfig) != update.state.facet(selectionConfig);
9409}
9410const cursorLayer = layer({
9411 above: true,
9412 markers(view) {
9413 let { state: state$1 } = view, conf = state$1.facet(selectionConfig);
9414 let cursors = [];
9415 for (let r of state$1.selection.ranges) {
9416 let prim = r == state$1.selection.main;
9417 if (r.empty || conf.drawRangeCursor) {
9418 let className = prim ? "cm-cursor cm-cursor-primary" : "cm-cursor cm-cursor-secondary";
9419 let cursor = r.empty ? r : state.EditorSelection.cursor(r.head, r.head > r.anchor ? -1 : 1);
9420 for (let piece of RectangleMarker.forRange(view, className, cursor))
9421 cursors.push(piece);
9422 }
9423 }
9424 return cursors;
9425 },
9426 update(update, dom) {
9427 if (update.transactions.some(tr => tr.selection))
9428 dom.style.animationName = dom.style.animationName == "cm-blink" ? "cm-blink2" : "cm-blink";
9429 let confChange = configChanged(update);
9430 if (confChange)
9431 setBlinkRate(update.state, dom);
9432 return update.docChanged || update.selectionSet || confChange;
9433 },
9434 mount(dom, view) {
9435 setBlinkRate(view.state, dom);
9436 },
9437 class: "cm-cursorLayer"
9438});
9439function setBlinkRate(state, dom) {
9440 dom.style.animationDuration = state.facet(selectionConfig).cursorBlinkRate + "ms";
9441}
9442const selectionLayer = layer({
9443 above: false,
9444 markers(view) {
9445 return view.state.selection.ranges.map(r => r.empty ? [] : RectangleMarker.forRange(view, "cm-selectionBackground", r))
9446 .reduce((a, b) => a.concat(b));
9447 },
9448 update(update, dom) {
9449 return update.docChanged || update.selectionSet || update.viewportChanged || configChanged(update);
9450 },
9451 class: "cm-selectionLayer"
9452});
9453const hideNativeSelection = state.Prec.highest(EditorView.theme({
9454 ".cm-line": {
9455 "& ::selection, &::selection": { backgroundColor: "transparent !important" },
9456 caretColor: "transparent !important"
9457 },
9458 ".cm-content": {
9459 caretColor: "transparent !important",
9460 "& :focus": {
9461 caretColor: "initial !important",
9462 "&::selection, & ::selection": {
9463 backgroundColor: "Highlight !important"
9464 }
9465 }
9466 }
9467}));
9468
9469const setDropCursorPos = state.StateEffect.define({
9470 map(pos, mapping) { return pos == null ? null : mapping.mapPos(pos); }
9471});
9472const dropCursorPos = state.StateField.define({
9473 create() { return null; },
9474 update(pos, tr) {
9475 if (pos != null)
9476 pos = tr.changes.mapPos(pos);
9477 return tr.effects.reduce((pos, e) => e.is(setDropCursorPos) ? e.value : pos, pos);
9478 }
9479});
9480const drawDropCursor = ViewPlugin.fromClass(class {
9481 constructor(view) {
9482 this.view = view;
9483 this.cursor = null;
9484 this.measureReq = { read: this.readPos.bind(this), write: this.drawCursor.bind(this) };
9485 }
9486 update(update) {
9487 var _a;
9488 let cursorPos = update.state.field(dropCursorPos);
9489 if (cursorPos == null) {
9490 if (this.cursor != null) {
9491 (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.remove();
9492 this.cursor = null;
9493 }
9494 }
9495 else {
9496 if (!this.cursor) {
9497 this.cursor = this.view.scrollDOM.appendChild(document.createElement("div"));
9498 this.cursor.className = "cm-dropCursor";
9499 }
9500 if (update.startState.field(dropCursorPos) != cursorPos || update.docChanged || update.geometryChanged)
9501 this.view.requestMeasure(this.measureReq);
9502 }
9503 }
9504 readPos() {
9505 let { view } = this;
9506 let pos = view.state.field(dropCursorPos);
9507 let rect = pos != null && view.coordsAtPos(pos);
9508 if (!rect)
9509 return null;
9510 let outer = view.scrollDOM.getBoundingClientRect();
9511 return {
9512 left: rect.left - outer.left + view.scrollDOM.scrollLeft * view.scaleX,
9513 top: rect.top - outer.top + view.scrollDOM.scrollTop * view.scaleY,
9514 height: rect.bottom - rect.top
9515 };
9516 }
9517 drawCursor(pos) {
9518 if (this.cursor) {
9519 let { scaleX, scaleY } = this.view;
9520 if (pos) {
9521 this.cursor.style.left = pos.left / scaleX + "px";
9522 this.cursor.style.top = pos.top / scaleY + "px";
9523 this.cursor.style.height = pos.height / scaleY + "px";
9524 }
9525 else {
9526 this.cursor.style.left = "-100000px";
9527 }
9528 }
9529 }
9530 destroy() {
9531 if (this.cursor)
9532 this.cursor.remove();
9533 }
9534 setDropPos(pos) {
9535 if (this.view.state.field(dropCursorPos) != pos)
9536 this.view.dispatch({ effects: setDropCursorPos.of(pos) });
9537 }
9538}, {
9539 eventObservers: {
9540 dragover(event) {
9541 this.setDropPos(this.view.posAtCoords({ x: event.clientX, y: event.clientY }));
9542 },
9543 dragleave(event) {
9544 if (event.target == this.view.contentDOM || !this.view.contentDOM.contains(event.relatedTarget))
9545 this.setDropPos(null);
9546 },
9547 dragend() {
9548 this.setDropPos(null);
9549 },
9550 drop() {
9551 this.setDropPos(null);
9552 }
9553 }
9554});
9555/**
9556Draws a cursor at the current drop position when something is
9557dragged over the editor.
9558*/
9559function dropCursor() {
9560 return [dropCursorPos, drawDropCursor];
9561}
9562
9563function iterMatches(doc, re, from, to, f) {
9564 re.lastIndex = 0;
9565 for (let cursor = doc.iterRange(from, to), pos = from, m; !cursor.next().done; pos += cursor.value.length) {
9566 if (!cursor.lineBreak)
9567 while (m = re.exec(cursor.value))
9568 f(pos + m.index, m);
9569 }
9570}
9571function matchRanges(view, maxLength) {
9572 let visible = view.visibleRanges;
9573 if (visible.length == 1 && visible[0].from == view.viewport.from &&
9574 visible[0].to == view.viewport.to)
9575 return visible;
9576 let result = [];
9577 for (let { from, to } of visible) {
9578 from = Math.max(view.state.doc.lineAt(from).from, from - maxLength);
9579 to = Math.min(view.state.doc.lineAt(to).to, to + maxLength);
9580 if (result.length && result[result.length - 1].to >= from)
9581 result[result.length - 1].to = to;
9582 else
9583 result.push({ from, to });
9584 }
9585 return result;
9586}
9587/**
9588Helper class used to make it easier to maintain decorations on
9589visible code that matches a given regular expression. To be used
9590in a [view plugin](https://codemirror.net/6/docs/ref/#view.ViewPlugin). Instances of this object
9591represent a matching configuration.
9592*/
9593class MatchDecorator {
9594 /**
9595 Create a decorator.
9596 */
9597 constructor(config) {
9598 const { regexp, decoration, decorate, boundary, maxLength = 1000 } = config;
9599 if (!regexp.global)
9600 throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
9601 this.regexp = regexp;
9602 if (decorate) {
9603 this.addMatch = (match, view, from, add) => decorate(add, from, from + match[0].length, match, view);
9604 }
9605 else if (typeof decoration == "function") {
9606 this.addMatch = (match, view, from, add) => {
9607 let deco = decoration(match, view, from);
9608 if (deco)
9609 add(from, from + match[0].length, deco);
9610 };
9611 }
9612 else if (decoration) {
9613 this.addMatch = (match, _view, from, add) => add(from, from + match[0].length, decoration);
9614 }
9615 else {
9616 throw new RangeError("Either 'decorate' or 'decoration' should be provided to MatchDecorator");
9617 }
9618 this.boundary = boundary;
9619 this.maxLength = maxLength;
9620 }
9621 /**
9622 Compute the full set of decorations for matches in the given
9623 view's viewport. You'll want to call this when initializing your
9624 plugin.
9625 */
9626 createDeco(view) {
9627 let build = new state.RangeSetBuilder(), add = build.add.bind(build);
9628 for (let { from, to } of matchRanges(view, this.maxLength))
9629 iterMatches(view.state.doc, this.regexp, from, to, (from, m) => this.addMatch(m, view, from, add));
9630 return build.finish();
9631 }
9632 /**
9633 Update a set of decorations for a view update. `deco` _must_ be
9634 the set of decorations produced by _this_ `MatchDecorator` for
9635 the view state before the update.
9636 */
9637 updateDeco(update, deco) {
9638 let changeFrom = 1e9, changeTo = -1;
9639 if (update.docChanged)
9640 update.changes.iterChanges((_f, _t, from, to) => {
9641 if (to >= update.view.viewport.from && from <= update.view.viewport.to) {
9642 changeFrom = Math.min(from, changeFrom);
9643 changeTo = Math.max(to, changeTo);
9644 }
9645 });
9646 if (update.viewportMoved || changeTo - changeFrom > 1000)
9647 return this.createDeco(update.view);
9648 if (changeTo > -1)
9649 return this.updateRange(update.view, deco.map(update.changes), changeFrom, changeTo);
9650 return deco;
9651 }
9652 updateRange(view, deco, updateFrom, updateTo) {
9653 for (let r of view.visibleRanges) {
9654 let from = Math.max(r.from, updateFrom), to = Math.min(r.to, updateTo);
9655 if (to >= from) {
9656 let fromLine = view.state.doc.lineAt(from), toLine = fromLine.to < to ? view.state.doc.lineAt(to) : fromLine;
9657 let start = Math.max(r.from, fromLine.from), end = Math.min(r.to, toLine.to);
9658 if (this.boundary) {
9659 for (; from > fromLine.from; from--)
9660 if (this.boundary.test(fromLine.text[from - 1 - fromLine.from])) {
9661 start = from;
9662 break;
9663 }
9664 for (; to < toLine.to; to++)
9665 if (this.boundary.test(toLine.text[to - toLine.from])) {
9666 end = to;
9667 break;
9668 }
9669 }
9670 let ranges = [], m;
9671 let add = (from, to, deco) => ranges.push(deco.range(from, to));
9672 if (fromLine == toLine) {
9673 this.regexp.lastIndex = start - fromLine.from;
9674 while ((m = this.regexp.exec(fromLine.text)) && m.index < end - fromLine.from)
9675 this.addMatch(m, view, m.index + fromLine.from, add);
9676 }
9677 else {
9678 iterMatches(view.state.doc, this.regexp, start, end, (from, m) => this.addMatch(m, view, from, add));
9679 }
9680 deco = deco.update({ filterFrom: start, filterTo: end, filter: (from, to) => from < start || to > end, add: ranges });
9681 }
9682 }
9683 return deco;
9684 }
9685}
9686
9687const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
9688const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
9689const Names = {
9690 0: "null",
9691 7: "bell",
9692 8: "backspace",
9693 10: "newline",
9694 11: "vertical tab",
9695 13: "carriage return",
9696 27: "escape",
9697 8203: "zero width space",
9698 8204: "zero width non-joiner",
9699 8205: "zero width joiner",
9700 8206: "left-to-right mark",
9701 8207: "right-to-left mark",
9702 8232: "line separator",
9703 8237: "left-to-right override",
9704 8238: "right-to-left override",
9705 8294: "left-to-right isolate",
9706 8295: "right-to-left isolate",
9707 8297: "pop directional isolate",
9708 8233: "paragraph separator",
9709 65279: "zero width no-break space",
9710 65532: "object replacement"
9711};
9712let _supportsTabSize = null;
9713function supportsTabSize() {
9714 var _a;
9715 if (_supportsTabSize == null && typeof document != "undefined" && document.body) {
9716 let styles = document.body.style;
9717 _supportsTabSize = ((_a = styles.tabSize) !== null && _a !== void 0 ? _a : styles.MozTabSize) != null;
9718 }
9719 return _supportsTabSize || false;
9720}
9721const specialCharConfig = state.Facet.define({
9722 combine(configs) {
9723 let config = state.combineConfig(configs, {
9724 render: null,
9725 specialChars: Specials,
9726 addSpecialChars: null
9727 });
9728 if (config.replaceTabs = !supportsTabSize())
9729 config.specialChars = new RegExp("\t|" + config.specialChars.source, UnicodeRegexpSupport);
9730 if (config.addSpecialChars)
9731 config.specialChars = new RegExp(config.specialChars.source + "|" + config.addSpecialChars.source, UnicodeRegexpSupport);
9732 return config;
9733 }
9734});
9735/**
9736Returns an extension that installs highlighting of special
9737characters.
9738*/
9739function highlightSpecialChars(
9740/**
9741Configuration options.
9742*/
9743config = {}) {
9744 return [specialCharConfig.of(config), specialCharPlugin()];
9745}
9746let _plugin = null;
9747function specialCharPlugin() {
9748 return _plugin || (_plugin = ViewPlugin.fromClass(class {
9749 constructor(view) {
9750 this.view = view;
9751 this.decorations = Decoration.none;
9752 this.decorationCache = Object.create(null);
9753 this.decorator = this.makeDecorator(view.state.facet(specialCharConfig));
9754 this.decorations = this.decorator.createDeco(view);
9755 }
9756 makeDecorator(conf) {
9757 return new MatchDecorator({
9758 regexp: conf.specialChars,
9759 decoration: (m, view, pos) => {
9760 let { doc } = view.state;
9761 let code = state.codePointAt(m[0], 0);
9762 if (code == 9) {
9763 let line = doc.lineAt(pos);
9764 let size = view.state.tabSize, col = state.countColumn(line.text, size, pos - line.from);
9765 return Decoration.replace({
9766 widget: new TabWidget((size - (col % size)) * this.view.defaultCharacterWidth / this.view.scaleX)
9767 });
9768 }
9769 return this.decorationCache[code] ||
9770 (this.decorationCache[code] = Decoration.replace({ widget: new SpecialCharWidget(conf, code) }));
9771 },
9772 boundary: conf.replaceTabs ? undefined : /[^]/
9773 });
9774 }
9775 update(update) {
9776 let conf = update.state.facet(specialCharConfig);
9777 if (update.startState.facet(specialCharConfig) != conf) {
9778 this.decorator = this.makeDecorator(conf);
9779 this.decorations = this.decorator.createDeco(update.view);
9780 }
9781 else {
9782 this.decorations = this.decorator.updateDeco(update, this.decorations);
9783 }
9784 }
9785 }, {
9786 decorations: v => v.decorations
9787 }));
9788}
9789const DefaultPlaceholder = "\u2022";
9790// Assigns placeholder characters from the Control Pictures block to
9791// ASCII control characters
9792function placeholder$1(code) {
9793 if (code >= 32)
9794 return DefaultPlaceholder;
9795 if (code == 10)
9796 return "\u2424";
9797 return String.fromCharCode(9216 + code);
9798}
9799class SpecialCharWidget extends WidgetType {
9800 constructor(options, code) {
9801 super();
9802 this.options = options;
9803 this.code = code;
9804 }
9805 eq(other) { return other.code == this.code; }
9806 toDOM(view) {
9807 let ph = placeholder$1(this.code);
9808 let desc = view.state.phrase("Control character") + " " + (Names[this.code] || "0x" + this.code.toString(16));
9809 let custom = this.options.render && this.options.render(this.code, desc, ph);
9810 if (custom)
9811 return custom;
9812 let span = document.createElement("span");
9813 span.textContent = ph;
9814 span.title = desc;
9815 span.setAttribute("aria-label", desc);
9816 span.className = "cm-specialChar";
9817 return span;
9818 }
9819 ignoreEvent() { return false; }
9820}
9821class TabWidget extends WidgetType {
9822 constructor(width) {
9823 super();
9824 this.width = width;
9825 }
9826 eq(other) { return other.width == this.width; }
9827 toDOM() {
9828 let span = document.createElement("span");
9829 span.textContent = "\t";
9830 span.className = "cm-tab";
9831 span.style.width = this.width + "px";
9832 return span;
9833 }
9834 ignoreEvent() { return false; }
9835}
9836
9837const plugin = ViewPlugin.fromClass(class {
9838 constructor() {
9839 this.height = 1000;
9840 this.attrs = { style: "padding-bottom: 1000px" };
9841 }
9842 update(update) {
9843 let { view } = update;
9844 let height = view.viewState.editorHeight -
9845 view.defaultLineHeight - view.documentPadding.top - 0.5;
9846 if (height >= 0 && height != this.height) {
9847 this.height = height;
9848 this.attrs = { style: `padding-bottom: ${height}px` };
9849 }
9850 }
9851});
9852/**
9853Returns an extension that makes sure the content has a bottom
9854margin equivalent to the height of the editor, minus one line
9855height, so that every line in the document can be scrolled to the
9856top of the editor.
9857
9858This is only meaningful when the editor is scrollable, and should
9859not be enabled in editors that take the size of their content.
9860*/
9861function scrollPastEnd() {
9862 return [plugin, contentAttributes.of(view => { var _a; return ((_a = view.plugin(plugin)) === null || _a === void 0 ? void 0 : _a.attrs) || null; })];
9863}
9864
9865/**
9866Mark lines that have a cursor on them with the `"cm-activeLine"`
9867DOM class.
9868*/
9869function highlightActiveLine() {
9870 return activeLineHighlighter;
9871}
9872const lineDeco = Decoration.line({ class: "cm-activeLine" });
9873const activeLineHighlighter = ViewPlugin.fromClass(class {
9874 constructor(view) {
9875 this.decorations = this.getDeco(view);
9876 }
9877 update(update) {
9878 if (update.docChanged || update.selectionSet)
9879 this.decorations = this.getDeco(update.view);
9880 }
9881 getDeco(view) {
9882 let lastLineStart = -1, deco = [];
9883 for (let r of view.state.selection.ranges) {
9884 let line = view.lineBlockAt(r.head);
9885 if (line.from > lastLineStart) {
9886 deco.push(lineDeco.range(line.from));
9887 lastLineStart = line.from;
9888 }
9889 }
9890 return Decoration.set(deco);
9891 }
9892}, {
9893 decorations: v => v.decorations
9894});
9895
9896class Placeholder extends WidgetType {
9897 constructor(content) {
9898 super();
9899 this.content = content;
9900 }
9901 toDOM(view) {
9902 let wrap = document.createElement("span");
9903 wrap.className = "cm-placeholder";
9904 wrap.style.pointerEvents = "none";
9905 wrap.appendChild(typeof this.content == "string" ? document.createTextNode(this.content) :
9906 typeof this.content == "function" ? this.content(view) :
9907 this.content.cloneNode(true));
9908 wrap.setAttribute("aria-hidden", "true");
9909 return wrap;
9910 }
9911 coordsAt(dom) {
9912 let rects = dom.firstChild ? clientRectsFor(dom.firstChild) : [];
9913 if (!rects.length)
9914 return null;
9915 let style = window.getComputedStyle(dom.parentNode);
9916 let rect = flattenRect(rects[0], style.direction != "rtl");
9917 let lineHeight = parseInt(style.lineHeight);
9918 if (rect.bottom - rect.top > lineHeight * 1.5)
9919 return { left: rect.left, right: rect.right, top: rect.top, bottom: rect.top + lineHeight };
9920 return rect;
9921 }
9922 ignoreEvent() { return false; }
9923}
9924/**
9925Extension that enables a placeholder—a piece of example content
9926to show when the editor is empty.
9927*/
9928function placeholder(content) {
9929 let plugin = ViewPlugin.fromClass(class {
9930 constructor(view) {
9931 this.view = view;
9932 this.placeholder = content
9933 ? Decoration.set([Decoration.widget({ widget: new Placeholder(content), side: 1 }).range(0)])
9934 : Decoration.none;
9935 }
9936 get decorations() { return this.view.state.doc.length ? Decoration.none : this.placeholder; }
9937 }, { decorations: v => v.decorations });
9938 return typeof content == "string" ? [
9939 plugin, EditorView.contentAttributes.of({ "aria-placeholder": content })
9940 ] : plugin;
9941}
9942
9943// Don't compute precise column positions for line offsets above this
9944// (since it could get expensive). Assume offset==column for them.
9945const MaxOff = 2000;
9946function rectangleFor(state$1, a, b) {
9947 let startLine = Math.min(a.line, b.line), endLine = Math.max(a.line, b.line);
9948 let ranges = [];
9949 if (a.off > MaxOff || b.off > MaxOff || a.col < 0 || b.col < 0) {
9950 let startOff = Math.min(a.off, b.off), endOff = Math.max(a.off, b.off);
9951 for (let i = startLine; i <= endLine; i++) {
9952 let line = state$1.doc.line(i);
9953 if (line.length <= endOff)
9954 ranges.push(state.EditorSelection.range(line.from + startOff, line.to + endOff));
9955 }
9956 }
9957 else {
9958 let startCol = Math.min(a.col, b.col), endCol = Math.max(a.col, b.col);
9959 for (let i = startLine; i <= endLine; i++) {
9960 let line = state$1.doc.line(i);
9961 let start = state.findColumn(line.text, startCol, state$1.tabSize, true);
9962 if (start < 0) {
9963 ranges.push(state.EditorSelection.cursor(line.to));
9964 }
9965 else {
9966 let end = state.findColumn(line.text, endCol, state$1.tabSize);
9967 ranges.push(state.EditorSelection.range(line.from + start, line.from + end));
9968 }
9969 }
9970 }
9971 return ranges;
9972}
9973function absoluteColumn(view, x) {
9974 let ref = view.coordsAtPos(view.viewport.from);
9975 return ref ? Math.round(Math.abs((ref.left - x) / view.defaultCharacterWidth)) : -1;
9976}
9977function getPos(view, event) {
9978 let offset = view.posAtCoords({ x: event.clientX, y: event.clientY }, false);
9979 let line = view.state.doc.lineAt(offset), off = offset - line.from;
9980 let col = off > MaxOff ? -1
9981 : off == line.length ? absoluteColumn(view, event.clientX)
9982 : state.countColumn(line.text, view.state.tabSize, offset - line.from);
9983 return { line: line.number, col, off };
9984}
9985function rectangleSelectionStyle(view, event) {
9986 let start = getPos(view, event), startSel = view.state.selection;
9987 if (!start)
9988 return null;
9989 return {
9990 update(update) {
9991 if (update.docChanged) {
9992 let newStart = update.changes.mapPos(update.startState.doc.line(start.line).from);
9993 let newLine = update.state.doc.lineAt(newStart);
9994 start = { line: newLine.number, col: start.col, off: Math.min(start.off, newLine.length) };
9995 startSel = startSel.map(update.changes);
9996 }
9997 },
9998 get(event, _extend, multiple) {
9999 let cur = getPos(view, event);
10000 if (!cur)
10001 return startSel;
10002 let ranges = rectangleFor(view.state, start, cur);
10003 if (!ranges.length)
10004 return startSel;
10005 if (multiple)
10006 return state.EditorSelection.create(ranges.concat(startSel.ranges));
10007 else
10008 return state.EditorSelection.create(ranges);
10009 }
10010 };
10011}
10012/**
10013Create an extension that enables rectangular selections. By
10014default, it will react to left mouse drag with the Alt key held
10015down. When such a selection occurs, the text within the rectangle
10016that was dragged over will be selected, as one selection
10017[range](https://codemirror.net/6/docs/ref/#state.SelectionRange) per line.
10018*/
10019function rectangularSelection(options) {
10020 let filter = (options === null || options === void 0 ? void 0 : options.eventFilter) || (e => e.altKey && e.button == 0);
10021 return EditorView.mouseSelectionStyle.of((view, event) => filter(event) ? rectangleSelectionStyle(view, event) : null);
10022}
10023const keys = {
10024 Alt: [18, e => !!e.altKey],
10025 Control: [17, e => !!e.ctrlKey],
10026 Shift: [16, e => !!e.shiftKey],
10027 Meta: [91, e => !!e.metaKey]
10028};
10029const showCrosshair = { style: "cursor: crosshair" };
10030/**
10031Returns an extension that turns the pointer cursor into a
10032crosshair when a given modifier key, defaulting to Alt, is held
10033down. Can serve as a visual hint that rectangular selection is
10034going to happen when paired with
10035[`rectangularSelection`](https://codemirror.net/6/docs/ref/#view.rectangularSelection).
10036*/
10037function crosshairCursor(options = {}) {
10038 let [code, getter] = keys[options.key || "Alt"];
10039 let plugin = ViewPlugin.fromClass(class {
10040 constructor(view) {
10041 this.view = view;
10042 this.isDown = false;
10043 }
10044 set(isDown) {
10045 if (this.isDown != isDown) {
10046 this.isDown = isDown;
10047 this.view.update([]);
10048 }
10049 }
10050 }, {
10051 eventObservers: {
10052 keydown(e) {
10053 this.set(e.keyCode == code || getter(e));
10054 },
10055 keyup(e) {
10056 if (e.keyCode == code || !getter(e))
10057 this.set(false);
10058 },
10059 mousemove(e) {
10060 this.set(getter(e));
10061 }
10062 }
10063 });
10064 return [
10065 plugin,
10066 EditorView.contentAttributes.of(view => { var _a; return ((_a = view.plugin(plugin)) === null || _a === void 0 ? void 0 : _a.isDown) ? showCrosshair : null; })
10067 ];
10068}
10069
10070const Outside = "-10000px";
10071class TooltipViewManager {
10072 constructor(view, facet, createTooltipView, removeTooltipView) {
10073 this.facet = facet;
10074 this.createTooltipView = createTooltipView;
10075 this.removeTooltipView = removeTooltipView;
10076 this.input = view.state.facet(facet);
10077 this.tooltips = this.input.filter(t => t);
10078 let prev = null;
10079 this.tooltipViews = this.tooltips.map(t => prev = createTooltipView(t, prev));
10080 }
10081 update(update, above) {
10082 var _a;
10083 let input = update.state.facet(this.facet);
10084 let tooltips = input.filter(x => x);
10085 if (input === this.input) {
10086 for (let t of this.tooltipViews)
10087 if (t.update)
10088 t.update(update);
10089 return false;
10090 }
10091 let tooltipViews = [], newAbove = above ? [] : null;
10092 for (let i = 0; i < tooltips.length; i++) {
10093 let tip = tooltips[i], known = -1;
10094 if (!tip)
10095 continue;
10096 for (let i = 0; i < this.tooltips.length; i++) {
10097 let other = this.tooltips[i];
10098 if (other && other.create == tip.create)
10099 known = i;
10100 }
10101 if (known < 0) {
10102 tooltipViews[i] = this.createTooltipView(tip, i ? tooltipViews[i - 1] : null);
10103 if (newAbove)
10104 newAbove[i] = !!tip.above;
10105 }
10106 else {
10107 let tooltipView = tooltipViews[i] = this.tooltipViews[known];
10108 if (newAbove)
10109 newAbove[i] = above[known];
10110 if (tooltipView.update)
10111 tooltipView.update(update);
10112 }
10113 }
10114 for (let t of this.tooltipViews)
10115 if (tooltipViews.indexOf(t) < 0) {
10116 this.removeTooltipView(t);
10117 (_a = t.destroy) === null || _a === void 0 ? void 0 : _a.call(t);
10118 }
10119 if (above) {
10120 newAbove.forEach((val, i) => above[i] = val);
10121 above.length = newAbove.length;
10122 }
10123 this.input = input;
10124 this.tooltips = tooltips;
10125 this.tooltipViews = tooltipViews;
10126 return true;
10127 }
10128}
10129/**
10130Creates an extension that configures tooltip behavior.
10131*/
10132function tooltips(config = {}) {
10133 return tooltipConfig.of(config);
10134}
10135function windowSpace(view) {
10136 let docElt = view.dom.ownerDocument.documentElement;
10137 return { top: 0, left: 0, bottom: docElt.clientHeight, right: docElt.clientWidth };
10138}
10139const tooltipConfig = state.Facet.define({
10140 combine: values => {
10141 var _a, _b, _c;
10142 return ({
10143 position: browser.ios ? "absolute" : ((_a = values.find(conf => conf.position)) === null || _a === void 0 ? void 0 : _a.position) || "fixed",
10144 parent: ((_b = values.find(conf => conf.parent)) === null || _b === void 0 ? void 0 : _b.parent) || null,
10145 tooltipSpace: ((_c = values.find(conf => conf.tooltipSpace)) === null || _c === void 0 ? void 0 : _c.tooltipSpace) || windowSpace,
10146 });
10147 }
10148});
10149const knownHeight = new WeakMap();
10150const tooltipPlugin = ViewPlugin.fromClass(class {
10151 constructor(view) {
10152 this.view = view;
10153 this.above = [];
10154 this.inView = true;
10155 this.madeAbsolute = false;
10156 this.lastTransaction = 0;
10157 this.measureTimeout = -1;
10158 let config = view.state.facet(tooltipConfig);
10159 this.position = config.position;
10160 this.parent = config.parent;
10161 this.classes = view.themeClasses;
10162 this.createContainer();
10163 this.measureReq = { read: this.readMeasure.bind(this), write: this.writeMeasure.bind(this), key: this };
10164 this.resizeObserver = typeof ResizeObserver == "function" ? new ResizeObserver(() => this.measureSoon()) : null;
10165 this.manager = new TooltipViewManager(view, showTooltip, (t, p) => this.createTooltip(t, p), t => {
10166 if (this.resizeObserver)
10167 this.resizeObserver.unobserve(t.dom);
10168 t.dom.remove();
10169 });
10170 this.above = this.manager.tooltips.map(t => !!t.above);
10171 this.intersectionObserver = typeof IntersectionObserver == "function" ? new IntersectionObserver(entries => {
10172 if (Date.now() > this.lastTransaction - 50 &&
10173 entries.length > 0 && entries[entries.length - 1].intersectionRatio < 1)
10174 this.measureSoon();
10175 }, { threshold: [1] }) : null;
10176 this.observeIntersection();
10177 view.win.addEventListener("resize", this.measureSoon = this.measureSoon.bind(this));
10178 this.maybeMeasure();
10179 }
10180 createContainer() {
10181 if (this.parent) {
10182 this.container = document.createElement("div");
10183 this.container.style.position = "relative";
10184 this.container.className = this.view.themeClasses;
10185 this.parent.appendChild(this.container);
10186 }
10187 else {
10188 this.container = this.view.dom;
10189 }
10190 }
10191 observeIntersection() {
10192 if (this.intersectionObserver) {
10193 this.intersectionObserver.disconnect();
10194 for (let tooltip of this.manager.tooltipViews)
10195 this.intersectionObserver.observe(tooltip.dom);
10196 }
10197 }
10198 measureSoon() {
10199 if (this.measureTimeout < 0)
10200 this.measureTimeout = setTimeout(() => {
10201 this.measureTimeout = -1;
10202 this.maybeMeasure();
10203 }, 50);
10204 }
10205 update(update) {
10206 if (update.transactions.length)
10207 this.lastTransaction = Date.now();
10208 let updated = this.manager.update(update, this.above);
10209 if (updated)
10210 this.observeIntersection();
10211 let shouldMeasure = updated || update.geometryChanged;
10212 let newConfig = update.state.facet(tooltipConfig);
10213 if (newConfig.position != this.position && !this.madeAbsolute) {
10214 this.position = newConfig.position;
10215 for (let t of this.manager.tooltipViews)
10216 t.dom.style.position = this.position;
10217 shouldMeasure = true;
10218 }
10219 if (newConfig.parent != this.parent) {
10220 if (this.parent)
10221 this.container.remove();
10222 this.parent = newConfig.parent;
10223 this.createContainer();
10224 for (let t of this.manager.tooltipViews)
10225 this.container.appendChild(t.dom);
10226 shouldMeasure = true;
10227 }
10228 else if (this.parent && this.view.themeClasses != this.classes) {
10229 this.classes = this.container.className = this.view.themeClasses;
10230 }
10231 if (shouldMeasure)
10232 this.maybeMeasure();
10233 }
10234 createTooltip(tooltip, prev) {
10235 let tooltipView = tooltip.create(this.view);
10236 let before = prev ? prev.dom : null;
10237 tooltipView.dom.classList.add("cm-tooltip");
10238 if (tooltip.arrow && !tooltipView.dom.querySelector(".cm-tooltip > .cm-tooltip-arrow")) {
10239 let arrow = document.createElement("div");
10240 arrow.className = "cm-tooltip-arrow";
10241 tooltipView.dom.appendChild(arrow);
10242 }
10243 tooltipView.dom.style.position = this.position;
10244 tooltipView.dom.style.top = Outside;
10245 tooltipView.dom.style.left = "0px";
10246 this.container.insertBefore(tooltipView.dom, before);
10247 if (tooltipView.mount)
10248 tooltipView.mount(this.view);
10249 if (this.resizeObserver)
10250 this.resizeObserver.observe(tooltipView.dom);
10251 return tooltipView;
10252 }
10253 destroy() {
10254 var _a, _b, _c;
10255 this.view.win.removeEventListener("resize", this.measureSoon);
10256 for (let tooltipView of this.manager.tooltipViews) {
10257 tooltipView.dom.remove();
10258 (_a = tooltipView.destroy) === null || _a === void 0 ? void 0 : _a.call(tooltipView);
10259 }
10260 if (this.parent)
10261 this.container.remove();
10262 (_b = this.resizeObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
10263 (_c = this.intersectionObserver) === null || _c === void 0 ? void 0 : _c.disconnect();
10264 clearTimeout(this.measureTimeout);
10265 }
10266 readMeasure() {
10267 let scaleX = 1, scaleY = 1, makeAbsolute = false;
10268 if (this.position == "fixed" && this.manager.tooltipViews.length) {
10269 let { dom } = this.manager.tooltipViews[0];
10270 if (browser.safari) {
10271 // Safari always sets offsetParent to null, even if a fixed
10272 // element is positioned relative to a transformed parent. So
10273 // we use this kludge to try and detect this.
10274 let rect = dom.getBoundingClientRect();
10275 makeAbsolute = Math.abs(rect.top + 10000) > 1 || Math.abs(rect.left) > 1;
10276 }
10277 else {
10278 // More conforming browsers will set offsetParent to the
10279 // transformed element.
10280 makeAbsolute = !!dom.offsetParent && dom.offsetParent != this.container.ownerDocument.body;
10281 }
10282 }
10283 if (makeAbsolute || this.position == "absolute") {
10284 if (this.parent) {
10285 let rect = this.parent.getBoundingClientRect();
10286 if (rect.width && rect.height) {
10287 scaleX = rect.width / this.parent.offsetWidth;
10288 scaleY = rect.height / this.parent.offsetHeight;
10289 }
10290 }
10291 else {
10292 ({ scaleX, scaleY } = this.view.viewState);
10293 }
10294 }
10295 let visible = this.view.scrollDOM.getBoundingClientRect(), margins = getScrollMargins(this.view);
10296 return {
10297 visible: {
10298 left: visible.left + margins.left, top: visible.top + margins.top,
10299 right: visible.right - margins.right, bottom: visible.bottom - margins.bottom
10300 },
10301 parent: this.parent ? this.container.getBoundingClientRect() : this.view.dom.getBoundingClientRect(),
10302 pos: this.manager.tooltips.map((t, i) => {
10303 let tv = this.manager.tooltipViews[i];
10304 return tv.getCoords ? tv.getCoords(t.pos) : this.view.coordsAtPos(t.pos);
10305 }),
10306 size: this.manager.tooltipViews.map(({ dom }) => dom.getBoundingClientRect()),
10307 space: this.view.state.facet(tooltipConfig).tooltipSpace(this.view),
10308 scaleX, scaleY, makeAbsolute
10309 };
10310 }
10311 writeMeasure(measured) {
10312 var _a;
10313 if (measured.makeAbsolute) {
10314 this.madeAbsolute = true;
10315 this.position = "absolute";
10316 for (let t of this.manager.tooltipViews)
10317 t.dom.style.position = "absolute";
10318 }
10319 let { visible, space, scaleX, scaleY } = measured;
10320 let others = [];
10321 for (let i = 0; i < this.manager.tooltips.length; i++) {
10322 let tooltip = this.manager.tooltips[i], tView = this.manager.tooltipViews[i], { dom } = tView;
10323 let pos = measured.pos[i], size = measured.size[i];
10324 // Hide tooltips that are outside of the editor.
10325 if (!pos || tooltip.clip !== false && (pos.bottom <= Math.max(visible.top, space.top) ||
10326 pos.top >= Math.min(visible.bottom, space.bottom) ||
10327 pos.right < Math.max(visible.left, space.left) - .1 ||
10328 pos.left > Math.min(visible.right, space.right) + .1)) {
10329 dom.style.top = Outside;
10330 continue;
10331 }
10332 let arrow = tooltip.arrow ? tView.dom.querySelector(".cm-tooltip-arrow") : null;
10333 let arrowHeight = arrow ? 7 /* Arrow.Size */ : 0;
10334 let width = size.right - size.left, height = (_a = knownHeight.get(tView)) !== null && _a !== void 0 ? _a : size.bottom - size.top;
10335 let offset = tView.offset || noOffset, ltr = this.view.textDirection == exports.Direction.LTR;
10336 let left = size.width > space.right - space.left
10337 ? (ltr ? space.left : space.right - size.width)
10338 : ltr ? Math.max(space.left, Math.min(pos.left - (arrow ? 14 /* Arrow.Offset */ : 0) + offset.x, space.right - width))
10339 : Math.min(Math.max(space.left, pos.left - width + (arrow ? 14 /* Arrow.Offset */ : 0) - offset.x), space.right - width);
10340 let above = this.above[i];
10341 if (!tooltip.strictSide && (above
10342 ? pos.top - height - arrowHeight - offset.y < space.top
10343 : pos.bottom + height + arrowHeight + offset.y > space.bottom) &&
10344 above == (space.bottom - pos.bottom > pos.top - space.top))
10345 above = this.above[i] = !above;
10346 let spaceVert = (above ? pos.top - space.top : space.bottom - pos.bottom) - arrowHeight;
10347 if (spaceVert < height && tView.resize !== false) {
10348 if (spaceVert < this.view.defaultLineHeight) {
10349 dom.style.top = Outside;
10350 continue;
10351 }
10352 knownHeight.set(tView, height);
10353 dom.style.height = (height = spaceVert) / scaleY + "px";
10354 }
10355 else if (dom.style.height) {
10356 dom.style.height = "";
10357 }
10358 let top = above ? pos.top - height - arrowHeight - offset.y : pos.bottom + arrowHeight + offset.y;
10359 let right = left + width;
10360 if (tView.overlap !== true)
10361 for (let r of others)
10362 if (r.left < right && r.right > left && r.top < top + height && r.bottom > top)
10363 top = above ? r.top - height - 2 - arrowHeight : r.bottom + arrowHeight + 2;
10364 if (this.position == "absolute") {
10365 dom.style.top = (top - measured.parent.top) / scaleY + "px";
10366 setLeftStyle(dom, (left - measured.parent.left) / scaleX);
10367 }
10368 else {
10369 dom.style.top = top / scaleY + "px";
10370 setLeftStyle(dom, left / scaleX);
10371 }
10372 if (arrow) {
10373 let arrowLeft = pos.left + (ltr ? offset.x : -offset.x) - (left + 14 /* Arrow.Offset */ - 7 /* Arrow.Size */);
10374 arrow.style.left = arrowLeft / scaleX + "px";
10375 }
10376 if (tView.overlap !== true)
10377 others.push({ left, top, right, bottom: top + height });
10378 dom.classList.toggle("cm-tooltip-above", above);
10379 dom.classList.toggle("cm-tooltip-below", !above);
10380 if (tView.positioned)
10381 tView.positioned(measured.space);
10382 }
10383 }
10384 maybeMeasure() {
10385 if (this.manager.tooltips.length) {
10386 if (this.view.inView)
10387 this.view.requestMeasure(this.measureReq);
10388 if (this.inView != this.view.inView) {
10389 this.inView = this.view.inView;
10390 if (!this.inView)
10391 for (let tv of this.manager.tooltipViews)
10392 tv.dom.style.top = Outside;
10393 }
10394 }
10395 }
10396}, {
10397 eventObservers: {
10398 scroll() { this.maybeMeasure(); }
10399 }
10400});
10401function setLeftStyle(elt, value) {
10402 let current = parseInt(elt.style.left, 10);
10403 if (isNaN(current) || Math.abs(value - current) > 1)
10404 elt.style.left = value + "px";
10405}
10406const baseTheme = EditorView.baseTheme({
10407 ".cm-tooltip": {
10408 zIndex: 500,
10409 boxSizing: "border-box"
10410 },
10411 "&light .cm-tooltip": {
10412 border: "1px solid #bbb",
10413 backgroundColor: "#f5f5f5"
10414 },
10415 "&light .cm-tooltip-section:not(:first-child)": {
10416 borderTop: "1px solid #bbb",
10417 },
10418 "&dark .cm-tooltip": {
10419 backgroundColor: "#333338",
10420 color: "white"
10421 },
10422 ".cm-tooltip-arrow": {
10423 height: `${7 /* Arrow.Size */}px`,
10424 width: `${7 /* Arrow.Size */ * 2}px`,
10425 position: "absolute",
10426 zIndex: -1,
10427 overflow: "hidden",
10428 "&:before, &:after": {
10429 content: "''",
10430 position: "absolute",
10431 width: 0,
10432 height: 0,
10433 borderLeft: `${7 /* Arrow.Size */}px solid transparent`,
10434 borderRight: `${7 /* Arrow.Size */}px solid transparent`,
10435 },
10436 ".cm-tooltip-above &": {
10437 bottom: `-${7 /* Arrow.Size */}px`,
10438 "&:before": {
10439 borderTop: `${7 /* Arrow.Size */}px solid #bbb`,
10440 },
10441 "&:after": {
10442 borderTop: `${7 /* Arrow.Size */}px solid #f5f5f5`,
10443 bottom: "1px"
10444 }
10445 },
10446 ".cm-tooltip-below &": {
10447 top: `-${7 /* Arrow.Size */}px`,
10448 "&:before": {
10449 borderBottom: `${7 /* Arrow.Size */}px solid #bbb`,
10450 },
10451 "&:after": {
10452 borderBottom: `${7 /* Arrow.Size */}px solid #f5f5f5`,
10453 top: "1px"
10454 }
10455 },
10456 },
10457 "&dark .cm-tooltip .cm-tooltip-arrow": {
10458 "&:before": {
10459 borderTopColor: "#333338",
10460 borderBottomColor: "#333338"
10461 },
10462 "&:after": {
10463 borderTopColor: "transparent",
10464 borderBottomColor: "transparent"
10465 }
10466 }
10467});
10468const noOffset = { x: 0, y: 0 };
10469/**
10470Facet to which an extension can add a value to show a tooltip.
10471*/
10472const showTooltip = state.Facet.define({
10473 enables: [tooltipPlugin, baseTheme]
10474});
10475const showHoverTooltip = state.Facet.define({
10476 combine: inputs => inputs.reduce((a, i) => a.concat(i), [])
10477});
10478class HoverTooltipHost {
10479 // Needs to be static so that host tooltip instances always match
10480 static create(view) {
10481 return new HoverTooltipHost(view);
10482 }
10483 constructor(view) {
10484 this.view = view;
10485 this.mounted = false;
10486 this.dom = document.createElement("div");
10487 this.dom.classList.add("cm-tooltip-hover");
10488 this.manager = new TooltipViewManager(view, showHoverTooltip, (t, p) => this.createHostedView(t, p), t => t.dom.remove());
10489 }
10490 createHostedView(tooltip, prev) {
10491 let hostedView = tooltip.create(this.view);
10492 hostedView.dom.classList.add("cm-tooltip-section");
10493 this.dom.insertBefore(hostedView.dom, prev ? prev.dom.nextSibling : this.dom.firstChild);
10494 if (this.mounted && hostedView.mount)
10495 hostedView.mount(this.view);
10496 return hostedView;
10497 }
10498 mount(view) {
10499 for (let hostedView of this.manager.tooltipViews) {
10500 if (hostedView.mount)
10501 hostedView.mount(view);
10502 }
10503 this.mounted = true;
10504 }
10505 positioned(space) {
10506 for (let hostedView of this.manager.tooltipViews) {
10507 if (hostedView.positioned)
10508 hostedView.positioned(space);
10509 }
10510 }
10511 update(update) {
10512 this.manager.update(update);
10513 }
10514 destroy() {
10515 var _a;
10516 for (let t of this.manager.tooltipViews)
10517 (_a = t.destroy) === null || _a === void 0 ? void 0 : _a.call(t);
10518 }
10519 passProp(name) {
10520 let value = undefined;
10521 for (let view of this.manager.tooltipViews) {
10522 let given = view[name];
10523 if (given !== undefined) {
10524 if (value === undefined)
10525 value = given;
10526 else if (value !== given)
10527 return undefined;
10528 }
10529 }
10530 return value;
10531 }
10532 get offset() { return this.passProp("offset"); }
10533 get getCoords() { return this.passProp("getCoords"); }
10534 get overlap() { return this.passProp("overlap"); }
10535 get resize() { return this.passProp("resize"); }
10536}
10537const showHoverTooltipHost = showTooltip.compute([showHoverTooltip], state => {
10538 let tooltips = state.facet(showHoverTooltip);
10539 if (tooltips.length === 0)
10540 return null;
10541 return {
10542 pos: Math.min(...tooltips.map(t => t.pos)),
10543 end: Math.max(...tooltips.map(t => { var _a; return (_a = t.end) !== null && _a !== void 0 ? _a : t.pos; })),
10544 create: HoverTooltipHost.create,
10545 above: tooltips[0].above,
10546 arrow: tooltips.some(t => t.arrow),
10547 };
10548});
10549class HoverPlugin {
10550 constructor(view, source, field, setHover, hoverTime) {
10551 this.view = view;
10552 this.source = source;
10553 this.field = field;
10554 this.setHover = setHover;
10555 this.hoverTime = hoverTime;
10556 this.hoverTimeout = -1;
10557 this.restartTimeout = -1;
10558 this.pending = null;
10559 this.lastMove = { x: 0, y: 0, target: view.dom, time: 0 };
10560 this.checkHover = this.checkHover.bind(this);
10561 view.dom.addEventListener("mouseleave", this.mouseleave = this.mouseleave.bind(this));
10562 view.dom.addEventListener("mousemove", this.mousemove = this.mousemove.bind(this));
10563 }
10564 update() {
10565 if (this.pending) {
10566 this.pending = null;
10567 clearTimeout(this.restartTimeout);
10568 this.restartTimeout = setTimeout(() => this.startHover(), 20);
10569 }
10570 }
10571 get active() {
10572 return this.view.state.field(this.field);
10573 }
10574 checkHover() {
10575 this.hoverTimeout = -1;
10576 if (this.active.length)
10577 return;
10578 let hovered = Date.now() - this.lastMove.time;
10579 if (hovered < this.hoverTime)
10580 this.hoverTimeout = setTimeout(this.checkHover, this.hoverTime - hovered);
10581 else
10582 this.startHover();
10583 }
10584 startHover() {
10585 clearTimeout(this.restartTimeout);
10586 let { view, lastMove } = this;
10587 let tile = view.docView.tile.nearest(lastMove.target);
10588 if (!tile)
10589 return;
10590 let pos, side = 1;
10591 if (tile.isWidget()) {
10592 pos = tile.posAtStart;
10593 }
10594 else {
10595 pos = view.posAtCoords(lastMove);
10596 if (pos == null)
10597 return;
10598 let posCoords = view.coordsAtPos(pos);
10599 if (!posCoords ||
10600 lastMove.y < posCoords.top || lastMove.y > posCoords.bottom ||
10601 lastMove.x < posCoords.left - view.defaultCharacterWidth ||
10602 lastMove.x > posCoords.right + view.defaultCharacterWidth)
10603 return;
10604 let bidi = view.bidiSpans(view.state.doc.lineAt(pos)).find(s => s.from <= pos && s.to >= pos);
10605 let rtl = bidi && bidi.dir == exports.Direction.RTL ? -1 : 1;
10606 side = (lastMove.x < posCoords.left ? -rtl : rtl);
10607 }
10608 let open = this.source(view, pos, side);
10609 if (open === null || open === void 0 ? void 0 : open.then) {
10610 let pending = this.pending = { pos };
10611 open.then(result => {
10612 if (this.pending == pending) {
10613 this.pending = null;
10614 if (result && !(Array.isArray(result) && !result.length))
10615 view.dispatch({ effects: this.setHover.of(Array.isArray(result) ? result : [result]) });
10616 }
10617 }, e => logException(view.state, e, "hover tooltip"));
10618 }
10619 else if (open && !(Array.isArray(open) && !open.length)) {
10620 view.dispatch({ effects: this.setHover.of(Array.isArray(open) ? open : [open]) });
10621 }
10622 }
10623 get tooltip() {
10624 let plugin = this.view.plugin(tooltipPlugin);
10625 let index = plugin ? plugin.manager.tooltips.findIndex(t => t.create == HoverTooltipHost.create) : -1;
10626 return index > -1 ? plugin.manager.tooltipViews[index] : null;
10627 }
10628 mousemove(event) {
10629 var _a, _b;
10630 this.lastMove = { x: event.clientX, y: event.clientY, target: event.target, time: Date.now() };
10631 if (this.hoverTimeout < 0)
10632 this.hoverTimeout = setTimeout(this.checkHover, this.hoverTime);
10633 let { active, tooltip } = this;
10634 if (active.length && tooltip && !isInTooltip(tooltip.dom, event) || this.pending) {
10635 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;
10636 if ((pos == end ? this.view.posAtCoords(this.lastMove) != pos
10637 : !isOverRange(this.view, pos, end, event.clientX, event.clientY))) {
10638 this.view.dispatch({ effects: this.setHover.of([]) });
10639 this.pending = null;
10640 }
10641 }
10642 }
10643 mouseleave(event) {
10644 clearTimeout(this.hoverTimeout);
10645 this.hoverTimeout = -1;
10646 let { active } = this;
10647 if (active.length) {
10648 let { tooltip } = this;
10649 let inTooltip = tooltip && tooltip.dom.contains(event.relatedTarget);
10650 if (!inTooltip)
10651 this.view.dispatch({ effects: this.setHover.of([]) });
10652 else
10653 this.watchTooltipLeave(tooltip.dom);
10654 }
10655 }
10656 watchTooltipLeave(tooltip) {
10657 let watch = (event) => {
10658 tooltip.removeEventListener("mouseleave", watch);
10659 if (this.active.length && !this.view.dom.contains(event.relatedTarget))
10660 this.view.dispatch({ effects: this.setHover.of([]) });
10661 };
10662 tooltip.addEventListener("mouseleave", watch);
10663 }
10664 destroy() {
10665 clearTimeout(this.hoverTimeout);
10666 clearTimeout(this.restartTimeout);
10667 this.view.dom.removeEventListener("mouseleave", this.mouseleave);
10668 this.view.dom.removeEventListener("mousemove", this.mousemove);
10669 }
10670}
10671const tooltipMargin = 4;
10672function isInTooltip(tooltip, event) {
10673 let { left, right, top, bottom } = tooltip.getBoundingClientRect(), arrow;
10674 if (arrow = tooltip.querySelector(".cm-tooltip-arrow")) {
10675 let arrowRect = arrow.getBoundingClientRect();
10676 top = Math.min(arrowRect.top, top);
10677 bottom = Math.max(arrowRect.bottom, bottom);
10678 }
10679 return event.clientX >= left - tooltipMargin && event.clientX <= right + tooltipMargin &&
10680 event.clientY >= top - tooltipMargin && event.clientY <= bottom + tooltipMargin;
10681}
10682function isOverRange(view, from, to, x, y, margin) {
10683 let rect = view.scrollDOM.getBoundingClientRect();
10684 let docBottom = view.documentTop + view.documentPadding.top + view.contentHeight;
10685 if (rect.left > x || rect.right < x || rect.top > y || Math.min(rect.bottom, docBottom) < y)
10686 return false;
10687 let pos = view.posAtCoords({ x, y }, false);
10688 return pos >= from && pos <= to;
10689}
10690/**
10691Set up a hover tooltip, which shows up when the pointer hovers
10692over ranges of text. The callback is called when the mouse hovers
10693over the document text. It should, if there is a tooltip
10694associated with position `pos`, return the tooltip description
10695(either directly or in a promise). The `side` argument indicates
10696on which side of the position the pointer is—it will be -1 if the
10697pointer is before the position, 1 if after the position.
10698
10699Note that all hover tooltips are hosted within a single tooltip
10700container element. This allows multiple tooltips over the same
10701range to be "merged" together without overlapping.
10702
10703The return value is a valid [editor extension](https://codemirror.net/6/docs/ref/#state.Extension)
10704but also provides an `active` property holding a state field that
10705can be used to read the currently active tooltips produced by this
10706extension.
10707*/
10708function hoverTooltip(source, options = {}) {
10709 let setHover = state.StateEffect.define();
10710 let hoverState = state.StateField.define({
10711 create() { return []; },
10712 update(value, tr) {
10713 if (value.length) {
10714 if (options.hideOnChange && (tr.docChanged || tr.selection))
10715 value = [];
10716 else if (options.hideOn)
10717 value = value.filter(v => !options.hideOn(tr, v));
10718 if (tr.docChanged) {
10719 let mapped = [];
10720 for (let tooltip of value) {
10721 let newPos = tr.changes.mapPos(tooltip.pos, -1, state.MapMode.TrackDel);
10722 if (newPos != null) {
10723 let copy = Object.assign(Object.create(null), tooltip);
10724 copy.pos = newPos;
10725 if (copy.end != null)
10726 copy.end = tr.changes.mapPos(copy.end);
10727 mapped.push(copy);
10728 }
10729 }
10730 value = mapped;
10731 }
10732 }
10733 for (let effect of tr.effects) {
10734 if (effect.is(setHover))
10735 value = effect.value;
10736 if (effect.is(closeHoverTooltipEffect))
10737 value = [];
10738 }
10739 return value;
10740 },
10741 provide: f => showHoverTooltip.from(f)
10742 });
10743 return {
10744 active: hoverState,
10745 extension: [
10746 hoverState,
10747 ViewPlugin.define(view => new HoverPlugin(view, source, hoverState, setHover, options.hoverTime || 300 /* Hover.Time */)),
10748 showHoverTooltipHost
10749 ]
10750 };
10751}
10752/**
10753Get the active tooltip view for a given tooltip, if available.
10754*/
10755function getTooltip(view, tooltip) {
10756 let plugin = view.plugin(tooltipPlugin);
10757 if (!plugin)
10758 return null;
10759 let found = plugin.manager.tooltips.indexOf(tooltip);
10760 return found < 0 ? null : plugin.manager.tooltipViews[found];
10761}
10762/**
10763Returns true if any hover tooltips are currently active.
10764*/
10765function hasHoverTooltips(state) {
10766 return state.facet(showHoverTooltip).some(x => x);
10767}
10768const closeHoverTooltipEffect = state.StateEffect.define();
10769/**
10770Transaction effect that closes all hover tooltips.
10771*/
10772const closeHoverTooltips = closeHoverTooltipEffect.of(null);
10773/**
10774Tell the tooltip extension to recompute the position of the active
10775tooltips. This can be useful when something happens (such as a
10776re-positioning or CSS change affecting the editor) that could
10777invalidate the existing tooltip positions.
10778*/
10779function repositionTooltips(view) {
10780 let plugin = view.plugin(tooltipPlugin);
10781 if (plugin)
10782 plugin.maybeMeasure();
10783}
10784
10785const panelConfig = state.Facet.define({
10786 combine(configs) {
10787 let topContainer, bottomContainer;
10788 for (let c of configs) {
10789 topContainer = topContainer || c.topContainer;
10790 bottomContainer = bottomContainer || c.bottomContainer;
10791 }
10792 return { topContainer, bottomContainer };
10793 }
10794});
10795/**
10796Configures the panel-managing extension.
10797*/
10798function panels(config) {
10799 return config ? [panelConfig.of(config)] : [];
10800}
10801/**
10802Get the active panel created by the given constructor, if any.
10803This can be useful when you need access to your panels' DOM
10804structure.
10805*/
10806function getPanel(view, panel) {
10807 let plugin = view.plugin(panelPlugin);
10808 let index = plugin ? plugin.specs.indexOf(panel) : -1;
10809 return index > -1 ? plugin.panels[index] : null;
10810}
10811const panelPlugin = ViewPlugin.fromClass(class {
10812 constructor(view) {
10813 this.input = view.state.facet(showPanel);
10814 this.specs = this.input.filter(s => s);
10815 this.panels = this.specs.map(spec => spec(view));
10816 let conf = view.state.facet(panelConfig);
10817 this.top = new PanelGroup(view, true, conf.topContainer);
10818 this.bottom = new PanelGroup(view, false, conf.bottomContainer);
10819 this.top.sync(this.panels.filter(p => p.top));
10820 this.bottom.sync(this.panels.filter(p => !p.top));
10821 for (let p of this.panels) {
10822 p.dom.classList.add("cm-panel");
10823 if (p.mount)
10824 p.mount();
10825 }
10826 }
10827 update(update) {
10828 let conf = update.state.facet(panelConfig);
10829 if (this.top.container != conf.topContainer) {
10830 this.top.sync([]);
10831 this.top = new PanelGroup(update.view, true, conf.topContainer);
10832 }
10833 if (this.bottom.container != conf.bottomContainer) {
10834 this.bottom.sync([]);
10835 this.bottom = new PanelGroup(update.view, false, conf.bottomContainer);
10836 }
10837 this.top.syncClasses();
10838 this.bottom.syncClasses();
10839 let input = update.state.facet(showPanel);
10840 if (input != this.input) {
10841 let specs = input.filter(x => x);
10842 let panels = [], top = [], bottom = [], mount = [];
10843 for (let spec of specs) {
10844 let known = this.specs.indexOf(spec), panel;
10845 if (known < 0) {
10846 panel = spec(update.view);
10847 mount.push(panel);
10848 }
10849 else {
10850 panel = this.panels[known];
10851 if (panel.update)
10852 panel.update(update);
10853 }
10854 panels.push(panel);
10855 (panel.top ? top : bottom).push(panel);
10856 }
10857 this.specs = specs;
10858 this.panels = panels;
10859 this.top.sync(top);
10860 this.bottom.sync(bottom);
10861 for (let p of mount) {
10862 p.dom.classList.add("cm-panel");
10863 if (p.mount)
10864 p.mount();
10865 }
10866 }
10867 else {
10868 for (let p of this.panels)
10869 if (p.update)
10870 p.update(update);
10871 }
10872 }
10873 destroy() {
10874 this.top.sync([]);
10875 this.bottom.sync([]);
10876 }
10877}, {
10878 provide: plugin => EditorView.scrollMargins.of(view => {
10879 let value = view.plugin(plugin);
10880 return value && { top: value.top.scrollMargin(), bottom: value.bottom.scrollMargin() };
10881 })
10882});
10883class PanelGroup {
10884 constructor(view, top, container) {
10885 this.view = view;
10886 this.top = top;
10887 this.container = container;
10888 this.dom = undefined;
10889 this.classes = "";
10890 this.panels = [];
10891 this.syncClasses();
10892 }
10893 sync(panels) {
10894 for (let p of this.panels)
10895 if (p.destroy && panels.indexOf(p) < 0)
10896 p.destroy();
10897 this.panels = panels;
10898 this.syncDOM();
10899 }
10900 syncDOM() {
10901 if (this.panels.length == 0) {
10902 if (this.dom) {
10903 this.dom.remove();
10904 this.dom = undefined;
10905 }
10906 return;
10907 }
10908 if (!this.dom) {
10909 this.dom = document.createElement("div");
10910 this.dom.className = this.top ? "cm-panels cm-panels-top" : "cm-panels cm-panels-bottom";
10911 this.dom.style[this.top ? "top" : "bottom"] = "0";
10912 let parent = this.container || this.view.dom;
10913 parent.insertBefore(this.dom, this.top ? parent.firstChild : null);
10914 }
10915 let curDOM = this.dom.firstChild;
10916 for (let panel of this.panels) {
10917 if (panel.dom.parentNode == this.dom) {
10918 while (curDOM != panel.dom)
10919 curDOM = rm(curDOM);
10920 curDOM = curDOM.nextSibling;
10921 }
10922 else {
10923 this.dom.insertBefore(panel.dom, curDOM);
10924 }
10925 }
10926 while (curDOM)
10927 curDOM = rm(curDOM);
10928 }
10929 scrollMargin() {
10930 return !this.dom || this.container ? 0
10931 : Math.max(0, this.top ?
10932 this.dom.getBoundingClientRect().bottom - Math.max(0, this.view.scrollDOM.getBoundingClientRect().top) :
10933 Math.min(innerHeight, this.view.scrollDOM.getBoundingClientRect().bottom) - this.dom.getBoundingClientRect().top);
10934 }
10935 syncClasses() {
10936 if (!this.container || this.classes == this.view.themeClasses)
10937 return;
10938 for (let cls of this.classes.split(" "))
10939 if (cls)
10940 this.container.classList.remove(cls);
10941 for (let cls of (this.classes = this.view.themeClasses).split(" "))
10942 if (cls)
10943 this.container.classList.add(cls);
10944 }
10945}
10946function rm(node) {
10947 let next = node.nextSibling;
10948 node.remove();
10949 return next;
10950}
10951/**
10952Opening a panel is done by providing a constructor function for
10953the panel through this facet. (The panel is closed again when its
10954constructor is no longer provided.) Values of `null` are ignored.
10955*/
10956const showPanel = state.Facet.define({
10957 enables: panelPlugin
10958});
10959
10960/**
10961Show a panel above or below the editor to show the user a message
10962or prompt them for input. Returns an effect that can be dispatched
10963to close the dialog, and a promise that resolves when the dialog
10964is closed or a form inside of it is submitted.
10965
10966You are encouraged, if your handling of the result of the promise
10967dispatches a transaction, to include the `close` effect in it. If
10968you don't, this function will automatically dispatch a separate
10969transaction right after.
10970*/
10971function showDialog(view, config) {
10972 let resolve;
10973 let promise = new Promise(r => resolve = r);
10974 let panelCtor = (view) => createDialog(view, config, resolve);
10975 if (view.state.field(dialogField, false)) {
10976 view.dispatch({ effects: openDialogEffect.of(panelCtor) });
10977 }
10978 else {
10979 view.dispatch({ effects: state.StateEffect.appendConfig.of(dialogField.init(() => [panelCtor])) });
10980 }
10981 let close = closeDialogEffect.of(panelCtor);
10982 return { close, result: promise.then(form => {
10983 let queue = view.win.queueMicrotask || ((f) => view.win.setTimeout(f, 10));
10984 queue(() => {
10985 if (view.state.field(dialogField).indexOf(panelCtor) > -1)
10986 view.dispatch({ effects: close });
10987 });
10988 return form;
10989 }) };
10990}
10991/**
10992Find the [`Panel`](https://codemirror.net/6/docs/ref/#view.Panel) for an open dialog, using a class
10993name as identifier.
10994*/
10995function getDialog(view, className) {
10996 let dialogs = view.state.field(dialogField, false) || [];
10997 for (let open of dialogs) {
10998 let panel = getPanel(view, open);
10999 if (panel && panel.dom.classList.contains(className))
11000 return panel;
11001 }
11002 return null;
11003}
11004const dialogField = state.StateField.define({
11005 create() { return []; },
11006 update(dialogs, tr) {
11007 for (let e of tr.effects) {
11008 if (e.is(openDialogEffect))
11009 dialogs = [e.value].concat(dialogs);
11010 else if (e.is(closeDialogEffect))
11011 dialogs = dialogs.filter(d => d != e.value);
11012 }
11013 return dialogs;
11014 },
11015 provide: f => showPanel.computeN([f], state => state.field(f))
11016});
11017const openDialogEffect = state.StateEffect.define();
11018const closeDialogEffect = state.StateEffect.define();
11019function createDialog(view, config, result) {
11020 let content = config.content ? config.content(view, () => done(null)) : null;
11021 if (!content) {
11022 content = elt("form");
11023 if (config.input) {
11024 let input = elt("input", config.input);
11025 if (/^(text|password|number|email|tel|url)$/.test(input.type))
11026 input.classList.add("cm-textfield");
11027 if (!input.name)
11028 input.name = "input";
11029 content.appendChild(elt("label", (config.label || "") + ": ", input));
11030 }
11031 else {
11032 content.appendChild(document.createTextNode(config.label || ""));
11033 }
11034 content.appendChild(document.createTextNode(" "));
11035 content.appendChild(elt("button", { class: "cm-button", type: "submit" }, config.submitLabel || "OK"));
11036 }
11037 let forms = content.nodeName == "FORM" ? [content] : content.querySelectorAll("form");
11038 for (let i = 0; i < forms.length; i++) {
11039 let form = forms[i];
11040 form.addEventListener("keydown", (event) => {
11041 if (event.keyCode == 27) { // Escape
11042 event.preventDefault();
11043 done(null);
11044 }
11045 else if (event.keyCode == 13) { // Enter
11046 event.preventDefault();
11047 done(form);
11048 }
11049 });
11050 form.addEventListener("submit", (event) => {
11051 event.preventDefault();
11052 done(form);
11053 });
11054 }
11055 let panel = elt("div", content, elt("button", {
11056 onclick: () => done(null),
11057 "aria-label": view.state.phrase("close"),
11058 class: "cm-dialog-close",
11059 type: "button"
11060 }, ["×"]));
11061 if (config.class)
11062 panel.className = config.class;
11063 panel.classList.add("cm-dialog");
11064 function done(form) {
11065 if (panel.contains(panel.ownerDocument.activeElement))
11066 view.focus();
11067 result(form);
11068 }
11069 return {
11070 dom: panel,
11071 top: config.top,
11072 mount: () => {
11073 if (config.focus) {
11074 let focus;
11075 if (typeof config.focus == "string")
11076 focus = content.querySelector(config.focus);
11077 else
11078 focus = content.querySelector("input") || content.querySelector("button");
11079 if (focus && "select" in focus)
11080 focus.select();
11081 else if (focus && "focus" in focus)
11082 focus.focus();
11083 }
11084 }
11085 };
11086}
11087
11088/**
11089A gutter marker represents a bit of information attached to a line
11090in a specific gutter. Your own custom markers have to extend this
11091class.
11092*/
11093class GutterMarker extends state.RangeValue {
11094 /**
11095 @internal
11096 */
11097 compare(other) {
11098 return this == other || this.constructor == other.constructor && this.eq(other);
11099 }
11100 /**
11101 Compare this marker to another marker of the same type.
11102 */
11103 eq(other) { return false; }
11104 /**
11105 Called if the marker has a `toDOM` method and its representation
11106 was removed from a gutter.
11107 */
11108 destroy(dom) { }
11109}
11110GutterMarker.prototype.elementClass = "";
11111GutterMarker.prototype.toDOM = undefined;
11112GutterMarker.prototype.mapMode = state.MapMode.TrackBefore;
11113GutterMarker.prototype.startSide = GutterMarker.prototype.endSide = -1;
11114GutterMarker.prototype.point = true;
11115/**
11116Facet used to add a class to all gutter elements for a given line.
11117Markers given to this facet should _only_ define an
11118[`elementclass`](https://codemirror.net/6/docs/ref/#view.GutterMarker.elementClass), not a
11119[`toDOM`](https://codemirror.net/6/docs/ref/#view.GutterMarker.toDOM) (or the marker will appear
11120in all gutters for the line).
11121*/
11122const gutterLineClass = state.Facet.define();
11123/**
11124Facet used to add a class to all gutter elements next to a widget.
11125Should not provide widgets with a `toDOM` method.
11126*/
11127const gutterWidgetClass = state.Facet.define();
11128const defaults = {
11129 class: "",
11130 renderEmptyElements: false,
11131 elementStyle: "",
11132 markers: () => state.RangeSet.empty,
11133 lineMarker: () => null,
11134 widgetMarker: () => null,
11135 lineMarkerChange: null,
11136 initialSpacer: null,
11137 updateSpacer: null,
11138 domEventHandlers: {},
11139 side: "before"
11140};
11141const activeGutters = state.Facet.define();
11142/**
11143Define an editor gutter. The order in which the gutters appear is
11144determined by their extension priority.
11145*/
11146function gutter(config) {
11147 return [gutters(), activeGutters.of({ ...defaults, ...config })];
11148}
11149const unfixGutters = state.Facet.define({
11150 combine: values => values.some(x => x)
11151});
11152/**
11153The gutter-drawing plugin is automatically enabled when you add a
11154gutter, but you can use this function to explicitly configure it.
11155
11156Unless `fixed` is explicitly set to `false`, the gutters are
11157fixed, meaning they don't scroll along with the content
11158horizontally (except on Internet Explorer, which doesn't support
11159CSS [`position:
11160sticky`](https://developer.mozilla.org/en-US/docs/Web/CSS/position#sticky)).
11161*/
11162function gutters(config) {
11163 let result = [
11164 gutterView,
11165 ];
11166 if (config && config.fixed === false)
11167 result.push(unfixGutters.of(true));
11168 return result;
11169}
11170const gutterView = ViewPlugin.fromClass(class {
11171 constructor(view) {
11172 this.view = view;
11173 this.domAfter = null;
11174 this.prevViewport = view.viewport;
11175 this.dom = document.createElement("div");
11176 this.dom.className = "cm-gutters cm-gutters-before";
11177 this.dom.setAttribute("aria-hidden", "true");
11178 this.dom.style.minHeight = (this.view.contentHeight / this.view.scaleY) + "px";
11179 this.gutters = view.state.facet(activeGutters).map(conf => new SingleGutterView(view, conf));
11180 this.fixed = !view.state.facet(unfixGutters);
11181 for (let gutter of this.gutters) {
11182 if (gutter.config.side == "after")
11183 this.getDOMAfter().appendChild(gutter.dom);
11184 else
11185 this.dom.appendChild(gutter.dom);
11186 }
11187 if (this.fixed) {
11188 // FIXME IE11 fallback, which doesn't support position: sticky,
11189 // by using position: relative + event handlers that realign the
11190 // gutter (or just force fixed=false on IE11?)
11191 this.dom.style.position = "sticky";
11192 }
11193 this.syncGutters(false);
11194 view.scrollDOM.insertBefore(this.dom, view.contentDOM);
11195 }
11196 getDOMAfter() {
11197 if (!this.domAfter) {
11198 this.domAfter = document.createElement("div");
11199 this.domAfter.className = "cm-gutters cm-gutters-after";
11200 this.domAfter.setAttribute("aria-hidden", "true");
11201 this.domAfter.style.minHeight = (this.view.contentHeight / this.view.scaleY) + "px";
11202 this.domAfter.style.position = this.fixed ? "sticky" : "";
11203 this.view.scrollDOM.appendChild(this.domAfter);
11204 }
11205 return this.domAfter;
11206 }
11207 update(update) {
11208 if (this.updateGutters(update)) {
11209 // Detach during sync when the viewport changed significantly
11210 // (such as during scrolling), since for large updates that is
11211 // faster.
11212 let vpA = this.prevViewport, vpB = update.view.viewport;
11213 let vpOverlap = Math.min(vpA.to, vpB.to) - Math.max(vpA.from, vpB.from);
11214 this.syncGutters(vpOverlap < (vpB.to - vpB.from) * 0.8);
11215 }
11216 if (update.geometryChanged) {
11217 let min = (this.view.contentHeight / this.view.scaleY) + "px";
11218 this.dom.style.minHeight = min;
11219 if (this.domAfter)
11220 this.domAfter.style.minHeight = min;
11221 }
11222 if (this.view.state.facet(unfixGutters) != !this.fixed) {
11223 this.fixed = !this.fixed;
11224 this.dom.style.position = this.fixed ? "sticky" : "";
11225 if (this.domAfter)
11226 this.domAfter.style.position = this.fixed ? "sticky" : "";
11227 }
11228 this.prevViewport = update.view.viewport;
11229 }
11230 syncGutters(detach) {
11231 let after = this.dom.nextSibling;
11232 if (detach) {
11233 this.dom.remove();
11234 if (this.domAfter)
11235 this.domAfter.remove();
11236 }
11237 let lineClasses = state.RangeSet.iter(this.view.state.facet(gutterLineClass), this.view.viewport.from);
11238 let classSet = [];
11239 let contexts = this.gutters.map(gutter => new UpdateContext(gutter, this.view.viewport, -this.view.documentPadding.top));
11240 for (let line of this.view.viewportLineBlocks) {
11241 if (classSet.length)
11242 classSet = [];
11243 if (Array.isArray(line.type)) {
11244 let first = true;
11245 for (let b of line.type) {
11246 if (b.type == exports.BlockType.Text && first) {
11247 advanceCursor(lineClasses, classSet, b.from);
11248 for (let cx of contexts)
11249 cx.line(this.view, b, classSet);
11250 first = false;
11251 }
11252 else if (b.widget) {
11253 for (let cx of contexts)
11254 cx.widget(this.view, b);
11255 }
11256 }
11257 }
11258 else if (line.type == exports.BlockType.Text) {
11259 advanceCursor(lineClasses, classSet, line.from);
11260 for (let cx of contexts)
11261 cx.line(this.view, line, classSet);
11262 }
11263 else if (line.widget) {
11264 for (let cx of contexts)
11265 cx.widget(this.view, line);
11266 }
11267 }
11268 for (let cx of contexts)
11269 cx.finish();
11270 if (detach) {
11271 this.view.scrollDOM.insertBefore(this.dom, after);
11272 if (this.domAfter)
11273 this.view.scrollDOM.appendChild(this.domAfter);
11274 }
11275 }
11276 updateGutters(update) {
11277 let prev = update.startState.facet(activeGutters), cur = update.state.facet(activeGutters);
11278 let change = update.docChanged || update.heightChanged || update.viewportChanged ||
11279 !state.RangeSet.eq(update.startState.facet(gutterLineClass), update.state.facet(gutterLineClass), update.view.viewport.from, update.view.viewport.to);
11280 if (prev == cur) {
11281 for (let gutter of this.gutters)
11282 if (gutter.update(update))
11283 change = true;
11284 }
11285 else {
11286 change = true;
11287 let gutters = [];
11288 for (let conf of cur) {
11289 let known = prev.indexOf(conf);
11290 if (known < 0) {
11291 gutters.push(new SingleGutterView(this.view, conf));
11292 }
11293 else {
11294 this.gutters[known].update(update);
11295 gutters.push(this.gutters[known]);
11296 }
11297 }
11298 for (let g of this.gutters) {
11299 g.dom.remove();
11300 if (gutters.indexOf(g) < 0)
11301 g.destroy();
11302 }
11303 for (let g of gutters) {
11304 if (g.config.side == "after")
11305 this.getDOMAfter().appendChild(g.dom);
11306 else
11307 this.dom.appendChild(g.dom);
11308 }
11309 this.gutters = gutters;
11310 }
11311 return change;
11312 }
11313 destroy() {
11314 for (let view of this.gutters)
11315 view.destroy();
11316 this.dom.remove();
11317 if (this.domAfter)
11318 this.domAfter.remove();
11319 }
11320}, {
11321 provide: plugin => EditorView.scrollMargins.of(view => {
11322 let value = view.plugin(plugin);
11323 if (!value || value.gutters.length == 0 || !value.fixed)
11324 return null;
11325 let before = value.dom.offsetWidth * view.scaleX, after = value.domAfter ? value.domAfter.offsetWidth * view.scaleX : 0;
11326 return view.textDirection == exports.Direction.LTR
11327 ? { left: before, right: after }
11328 : { right: before, left: after };
11329 })
11330});
11331function asArray(val) { return (Array.isArray(val) ? val : [val]); }
11332function advanceCursor(cursor, collect, pos) {
11333 while (cursor.value && cursor.from <= pos) {
11334 if (cursor.from == pos)
11335 collect.push(cursor.value);
11336 cursor.next();
11337 }
11338}
11339class UpdateContext {
11340 constructor(gutter, viewport, height) {
11341 this.gutter = gutter;
11342 this.height = height;
11343 this.i = 0;
11344 this.cursor = state.RangeSet.iter(gutter.markers, viewport.from);
11345 }
11346 addElement(view, block, markers) {
11347 let { gutter } = this, above = (block.top - this.height) / view.scaleY, height = block.height / view.scaleY;
11348 if (this.i == gutter.elements.length) {
11349 let newElt = new GutterElement(view, height, above, markers);
11350 gutter.elements.push(newElt);
11351 gutter.dom.appendChild(newElt.dom);
11352 }
11353 else {
11354 gutter.elements[this.i].update(view, height, above, markers);
11355 }
11356 this.height = block.bottom;
11357 this.i++;
11358 }
11359 line(view, line, extraMarkers) {
11360 let localMarkers = [];
11361 advanceCursor(this.cursor, localMarkers, line.from);
11362 if (extraMarkers.length)
11363 localMarkers = localMarkers.concat(extraMarkers);
11364 let forLine = this.gutter.config.lineMarker(view, line, localMarkers);
11365 if (forLine)
11366 localMarkers.unshift(forLine);
11367 let gutter = this.gutter;
11368 if (localMarkers.length == 0 && !gutter.config.renderEmptyElements)
11369 return;
11370 this.addElement(view, line, localMarkers);
11371 }
11372 widget(view, block) {
11373 let marker = this.gutter.config.widgetMarker(view, block.widget, block), markers = marker ? [marker] : null;
11374 for (let cls of view.state.facet(gutterWidgetClass)) {
11375 let marker = cls(view, block.widget, block);
11376 if (marker)
11377 (markers || (markers = [])).push(marker);
11378 }
11379 if (markers)
11380 this.addElement(view, block, markers);
11381 }
11382 finish() {
11383 let gutter = this.gutter;
11384 while (gutter.elements.length > this.i) {
11385 let last = gutter.elements.pop();
11386 gutter.dom.removeChild(last.dom);
11387 last.destroy();
11388 }
11389 }
11390}
11391class SingleGutterView {
11392 constructor(view, config) {
11393 this.view = view;
11394 this.config = config;
11395 this.elements = [];
11396 this.spacer = null;
11397 this.dom = document.createElement("div");
11398 this.dom.className = "cm-gutter" + (this.config.class ? " " + this.config.class : "");
11399 for (let prop in config.domEventHandlers) {
11400 this.dom.addEventListener(prop, (event) => {
11401 let target = event.target, y;
11402 if (target != this.dom && this.dom.contains(target)) {
11403 while (target.parentNode != this.dom)
11404 target = target.parentNode;
11405 let rect = target.getBoundingClientRect();
11406 y = (rect.top + rect.bottom) / 2;
11407 }
11408 else {
11409 y = event.clientY;
11410 }
11411 let line = view.lineBlockAtHeight(y - view.documentTop);
11412 if (config.domEventHandlers[prop](view, line, event))
11413 event.preventDefault();
11414 });
11415 }
11416 this.markers = asArray(config.markers(view));
11417 if (config.initialSpacer) {
11418 this.spacer = new GutterElement(view, 0, 0, [config.initialSpacer(view)]);
11419 this.dom.appendChild(this.spacer.dom);
11420 this.spacer.dom.style.cssText += "visibility: hidden; pointer-events: none";
11421 }
11422 }
11423 update(update) {
11424 let prevMarkers = this.markers;
11425 this.markers = asArray(this.config.markers(update.view));
11426 if (this.spacer && this.config.updateSpacer) {
11427 let updated = this.config.updateSpacer(this.spacer.markers[0], update);
11428 if (updated != this.spacer.markers[0])
11429 this.spacer.update(update.view, 0, 0, [updated]);
11430 }
11431 let vp = update.view.viewport;
11432 return !state.RangeSet.eq(this.markers, prevMarkers, vp.from, vp.to) ||
11433 (this.config.lineMarkerChange ? this.config.lineMarkerChange(update) : false);
11434 }
11435 destroy() {
11436 for (let elt of this.elements)
11437 elt.destroy();
11438 }
11439}
11440class GutterElement {
11441 constructor(view, height, above, markers) {
11442 this.height = -1;
11443 this.above = 0;
11444 this.markers = [];
11445 this.dom = document.createElement("div");
11446 this.dom.className = "cm-gutterElement";
11447 this.update(view, height, above, markers);
11448 }
11449 update(view, height, above, markers) {
11450 if (this.height != height) {
11451 this.height = height;
11452 this.dom.style.height = height + "px";
11453 }
11454 if (this.above != above)
11455 this.dom.style.marginTop = (this.above = above) ? above + "px" : "";
11456 if (!sameMarkers(this.markers, markers))
11457 this.setMarkers(view, markers);
11458 }
11459 setMarkers(view, markers) {
11460 let cls = "cm-gutterElement", domPos = this.dom.firstChild;
11461 for (let iNew = 0, iOld = 0;;) {
11462 let skipTo = iOld, marker = iNew < markers.length ? markers[iNew++] : null, matched = false;
11463 if (marker) {
11464 let c = marker.elementClass;
11465 if (c)
11466 cls += " " + c;
11467 for (let i = iOld; i < this.markers.length; i++)
11468 if (this.markers[i].compare(marker)) {
11469 skipTo = i;
11470 matched = true;
11471 break;
11472 }
11473 }
11474 else {
11475 skipTo = this.markers.length;
11476 }
11477 while (iOld < skipTo) {
11478 let next = this.markers[iOld++];
11479 if (next.toDOM) {
11480 next.destroy(domPos);
11481 let after = domPos.nextSibling;
11482 domPos.remove();
11483 domPos = after;
11484 }
11485 }
11486 if (!marker)
11487 break;
11488 if (marker.toDOM) {
11489 if (matched)
11490 domPos = domPos.nextSibling;
11491 else
11492 this.dom.insertBefore(marker.toDOM(view), domPos);
11493 }
11494 if (matched)
11495 iOld++;
11496 }
11497 this.dom.className = cls;
11498 this.markers = markers;
11499 }
11500 destroy() {
11501 this.setMarkers(null, []); // First argument not used unless creating markers
11502 }
11503}
11504function sameMarkers(a, b) {
11505 if (a.length != b.length)
11506 return false;
11507 for (let i = 0; i < a.length; i++)
11508 if (!a[i].compare(b[i]))
11509 return false;
11510 return true;
11511}
11512/**
11513Facet used to provide markers to the line number gutter.
11514*/
11515const lineNumberMarkers = state.Facet.define();
11516/**
11517Facet used to create markers in the line number gutter next to widgets.
11518*/
11519const lineNumberWidgetMarker = state.Facet.define();
11520const lineNumberConfig = state.Facet.define({
11521 combine(values) {
11522 return state.combineConfig(values, { formatNumber: String, domEventHandlers: {} }, {
11523 domEventHandlers(a, b) {
11524 let result = Object.assign({}, a);
11525 for (let event in b) {
11526 let exists = result[event], add = b[event];
11527 result[event] = exists ? (view, line, event) => exists(view, line, event) || add(view, line, event) : add;
11528 }
11529 return result;
11530 }
11531 });
11532 }
11533});
11534class NumberMarker extends GutterMarker {
11535 constructor(number) {
11536 super();
11537 this.number = number;
11538 }
11539 eq(other) { return this.number == other.number; }
11540 toDOM() { return document.createTextNode(this.number); }
11541}
11542function formatNumber(view, number) {
11543 return view.state.facet(lineNumberConfig).formatNumber(number, view.state);
11544}
11545const lineNumberGutter = activeGutters.compute([lineNumberConfig], state => ({
11546 class: "cm-lineNumbers",
11547 renderEmptyElements: false,
11548 markers(view) { return view.state.facet(lineNumberMarkers); },
11549 lineMarker(view, line, others) {
11550 if (others.some(m => m.toDOM))
11551 return null;
11552 return new NumberMarker(formatNumber(view, view.state.doc.lineAt(line.from).number));
11553 },
11554 widgetMarker: (view, widget, block) => {
11555 for (let m of view.state.facet(lineNumberWidgetMarker)) {
11556 let result = m(view, widget, block);
11557 if (result)
11558 return result;
11559 }
11560 return null;
11561 },
11562 lineMarkerChange: update => update.startState.facet(lineNumberConfig) != update.state.facet(lineNumberConfig),
11563 initialSpacer(view) {
11564 return new NumberMarker(formatNumber(view, maxLineNumber(view.state.doc.lines)));
11565 },
11566 updateSpacer(spacer, update) {
11567 let max = formatNumber(update.view, maxLineNumber(update.view.state.doc.lines));
11568 return max == spacer.number ? spacer : new NumberMarker(max);
11569 },
11570 domEventHandlers: state.facet(lineNumberConfig).domEventHandlers,
11571 side: "before"
11572}));
11573/**
11574Create a line number gutter extension.
11575*/
11576function lineNumbers(config = {}) {
11577 return [
11578 lineNumberConfig.of(config),
11579 gutters(),
11580 lineNumberGutter
11581 ];
11582}
11583function maxLineNumber(lines) {
11584 let last = 9;
11585 while (last < lines)
11586 last = last * 10 + 9;
11587 return last;
11588}
11589const activeLineGutterMarker = new class extends GutterMarker {
11590 constructor() {
11591 super(...arguments);
11592 this.elementClass = "cm-activeLineGutter";
11593 }
11594};
11595const activeLineGutterHighlighter = gutterLineClass.compute(["selection"], state$1 => {
11596 let marks = [], last = -1;
11597 for (let range of state$1.selection.ranges) {
11598 let linePos = state$1.doc.lineAt(range.head).from;
11599 if (linePos > last) {
11600 last = linePos;
11601 marks.push(activeLineGutterMarker.range(linePos));
11602 }
11603 }
11604 return state.RangeSet.of(marks);
11605});
11606/**
11607Returns an extension that adds a `cm-activeLineGutter` class to
11608all gutter elements on the [active
11609line](https://codemirror.net/6/docs/ref/#view.highlightActiveLine).
11610*/
11611function highlightActiveLineGutter() {
11612 return activeLineGutterHighlighter;
11613}
11614
11615function matcher(decorator) {
11616 return ViewPlugin.define(view => ({
11617 decorations: decorator.createDeco(view),
11618 update(u) {
11619 this.decorations = decorator.updateDeco(u, this.decorations);
11620 },
11621 }), {
11622 decorations: v => v.decorations
11623 });
11624}
11625const tabDeco = Decoration.mark({ class: "cm-highlightTab" });
11626const spaceDeco = Decoration.mark({ class: "cm-highlightSpace" });
11627const whitespaceHighlighter = matcher(new MatchDecorator({
11628 regexp: /\t| /g,
11629 decoration: match => match[0] == "\t" ? tabDeco : spaceDeco,
11630 boundary: /\S/,
11631}));
11632/**
11633Returns an extension that highlights whitespace, adding a
11634`cm-highlightSpace` class to stretches of spaces, and a
11635`cm-highlightTab` class to individual tab characters. By default,
11636the former are shown as faint dots, and the latter as arrows.
11637*/
11638function highlightWhitespace() {
11639 return whitespaceHighlighter;
11640}
11641const trailingHighlighter = matcher(new MatchDecorator({
11642 regexp: /\s+$/g,
11643 decoration: Decoration.mark({ class: "cm-trailingSpace" })
11644}));
11645/**
11646Returns an extension that adds a `cm-trailingSpace` class to all
11647trailing whitespace.
11648*/
11649function highlightTrailingWhitespace() {
11650 return trailingHighlighter;
11651}
11652
11653/**
11654@internal
11655*/
11656const __test = { HeightMap, HeightOracle, MeasuredHeights, QueryType, ChangedRange, computeOrder,
11657 moveVisually, clearHeightChangeFlag, getHeightChangeFlag: () => heightChangeFlag };
11658
11659exports.BidiSpan = BidiSpan;
11660exports.BlockInfo = BlockInfo;
11661exports.BlockWrapper = BlockWrapper;
11662exports.Decoration = Decoration;
11663exports.EditorView = EditorView;
11664exports.GutterMarker = GutterMarker;
11665exports.MatchDecorator = MatchDecorator;
11666exports.RectangleMarker = RectangleMarker;
11667exports.ViewPlugin = ViewPlugin;
11668exports.ViewUpdate = ViewUpdate;
11669exports.WidgetType = WidgetType;
11670exports.__test = __test;
11671exports.closeHoverTooltips = closeHoverTooltips;
11672exports.crosshairCursor = crosshairCursor;
11673exports.drawSelection = drawSelection;
11674exports.dropCursor = dropCursor;
11675exports.getDialog = getDialog;
11676exports.getDrawSelectionConfig = getDrawSelectionConfig;
11677exports.getPanel = getPanel;
11678exports.getTooltip = getTooltip;
11679exports.gutter = gutter;
11680exports.gutterLineClass = gutterLineClass;
11681exports.gutterWidgetClass = gutterWidgetClass;
11682exports.gutters = gutters;
11683exports.hasHoverTooltips = hasHoverTooltips;
11684exports.highlightActiveLine = highlightActiveLine;
11685exports.highlightActiveLineGutter = highlightActiveLineGutter;
11686exports.highlightSpecialChars = highlightSpecialChars;
11687exports.highlightTrailingWhitespace = highlightTrailingWhitespace;
11688exports.highlightWhitespace = highlightWhitespace;
11689exports.hoverTooltip = hoverTooltip;
11690exports.keymap = keymap;
11691exports.layer = layer;
11692exports.lineNumberMarkers = lineNumberMarkers;
11693exports.lineNumberWidgetMarker = lineNumberWidgetMarker;
11694exports.lineNumbers = lineNumbers;
11695exports.logException = logException;
11696exports.panels = panels;
11697exports.placeholder = placeholder;
11698exports.rectangularSelection = rectangularSelection;
11699exports.repositionTooltips = repositionTooltips;
11700exports.runScopeHandlers = runScopeHandlers;
11701exports.scrollPastEnd = scrollPastEnd;
11702exports.showDialog = showDialog;
11703exports.showPanel = showPanel;
11704exports.showTooltip = showTooltip;
11705exports.tooltips = tooltips;