11355 lines
		
	
	
		
			453 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			11355 lines
		
	
	
		
			453 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import { RangeSet, MapMode, EditorSelection, RangeValue, Facet, ChangeSet, EditorState, Transaction, Prec, StateEffect, codePointAt, codePointSize, RangeSetBuilder, countColumn, StateField, findClusterBreak, findColumn, Text, Annotation, combineConfig, CharCategory } from '@codemirror/state';
 | |
| 
 | |
| const C = "\u037c";
 | |
| const COUNT = typeof Symbol == "undefined" ? "__" + C : Symbol.for(C);
 | |
| const SET = typeof Symbol == "undefined" ? "__styleSet" + Math.floor(Math.random() * 1e8) : Symbol("styleSet");
 | |
| const top = typeof globalThis != "undefined" ? globalThis : typeof window != "undefined" ? window : {};
 | |
| 
 | |
| // :: - Style modules encapsulate a set of CSS rules defined from
 | |
| // JavaScript. Their definitions are only available in a given DOM
 | |
| // root after it has been _mounted_ there with `StyleModule.mount`.
 | |
| //
 | |
| // Style modules should be created once and stored somewhere, as
 | |
| // opposed to re-creating them every time you need them. The amount of
 | |
| // CSS rules generated for a given DOM root is bounded by the amount
 | |
| // of style modules that were used. So to avoid leaking rules, don't
 | |
| // create these dynamically, but treat them as one-time allocations.
 | |
| class StyleModule {
 | |
|   // :: (Object<Style>, ?{finish: ?(string) → string})
 | |
|   // Create a style module from the given spec.
 | |
|   //
 | |
|   // When `finish` is given, it is called on regular (non-`@`)
 | |
|   // selectors (after `&` expansion) to compute the final selector.
 | |
|   constructor(spec, options) {
 | |
|     this.rules = [];
 | |
|     let {finish} = options || {};
 | |
| 
 | |
|     function splitSelector(selector) {
 | |
|       return /^@/.test(selector) ? [selector] : selector.split(/,\s*/)
 | |
|     }
 | |
| 
 | |
|     function render(selectors, spec, target, isKeyframes) {
 | |
|       let local = [], isAt = /^@(\w+)\b/.exec(selectors[0]), keyframes = isAt && isAt[1] == "keyframes";
 | |
|       if (isAt && spec == null) return target.push(selectors[0] + ";")
 | |
|       for (let prop in spec) {
 | |
|         let value = spec[prop];
 | |
|         if (/&/.test(prop)) {
 | |
|           render(prop.split(/,\s*/).map(part => selectors.map(sel => part.replace(/&/, sel))).reduce((a, b) => a.concat(b)),
 | |
|                  value, target);
 | |
|         } else if (value && typeof value == "object") {
 | |
|           if (!isAt) throw new RangeError("The value of a property (" + prop + ") should be a primitive value.")
 | |
|           render(splitSelector(prop), value, local, keyframes);
 | |
|         } else if (value != null) {
 | |
|           local.push(prop.replace(/_.*/, "").replace(/[A-Z]/g, l => "-" + l.toLowerCase()) + ": " + value + ";");
 | |
|         }
 | |
|       }
 | |
|       if (local.length || keyframes) {
 | |
|         target.push((finish && !isAt && !isKeyframes ? selectors.map(finish) : selectors).join(", ") +
 | |
|                     " {" + local.join(" ") + "}");
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     for (let prop in spec) render(splitSelector(prop), spec[prop], this.rules);
 | |
|   }
 | |
| 
 | |
|   // :: () → string
 | |
|   // Returns a string containing the module's CSS rules.
 | |
|   getRules() { return this.rules.join("\n") }
 | |
| 
 | |
|   // :: () → string
 | |
|   // Generate a new unique CSS class name.
 | |
|   static newName() {
 | |
|     let id = top[COUNT] || 1;
 | |
|     top[COUNT] = id + 1;
 | |
|     return C + id.toString(36)
 | |
|   }
 | |
| 
 | |
|   // :: (union<Document, ShadowRoot>, union<[StyleModule], StyleModule>, ?{nonce: ?string})
 | |
|   //
 | |
|   // Mount the given set of modules in the given DOM root, which ensures
 | |
|   // that the CSS rules defined by the module are available in that
 | |
|   // context.
 | |
|   //
 | |
|   // Rules are only added to the document once per root.
 | |
|   //
 | |
|   // Rule order will follow the order of the modules, so that rules from
 | |
|   // modules later in the array take precedence of those from earlier
 | |
|   // modules. If you call this function multiple times for the same root
 | |
|   // in a way that changes the order of already mounted modules, the old
 | |
|   // order will be changed.
 | |
|   //
 | |
|   // If a Content Security Policy nonce is provided, it is added to
 | |
|   // the `<style>` tag generated by the library.
 | |
|   static mount(root, modules, options) {
 | |
|     let set = root[SET], nonce = options && options.nonce;
 | |
|     if (!set) set = new StyleSet(root, nonce);
 | |
|     else if (nonce) set.setNonce(nonce);
 | |
|     set.mount(Array.isArray(modules) ? modules : [modules], root);
 | |
|   }
 | |
| }
 | |
| 
 | |
| let adoptedSet = new Map; //<Document, StyleSet>
 | |
| 
 | |
| class StyleSet {
 | |
|   constructor(root, nonce) {
 | |
|     let doc = root.ownerDocument || root, win = doc.defaultView;
 | |
|     if (!root.head && root.adoptedStyleSheets && win.CSSStyleSheet) {
 | |
|       let adopted = adoptedSet.get(doc);
 | |
|       if (adopted) return root[SET] = adopted
 | |
|       this.sheet = new win.CSSStyleSheet;
 | |
|       adoptedSet.set(doc, this);
 | |
|     } else {
 | |
|       this.styleTag = doc.createElement("style");
 | |
|       if (nonce) this.styleTag.setAttribute("nonce", nonce);
 | |
|     }
 | |
|     this.modules = [];
 | |
|     root[SET] = this;
 | |
|   }
 | |
| 
 | |
|   mount(modules, root) {
 | |
|     let sheet = this.sheet;
 | |
|     let pos = 0 /* Current rule offset */, j = 0; /* Index into this.modules */
 | |
|     for (let i = 0; i < modules.length; i++) {
 | |
|       let mod = modules[i], index = this.modules.indexOf(mod);
 | |
|       if (index < j && index > -1) { // Ordering conflict
 | |
|         this.modules.splice(index, 1);
 | |
|         j--;
 | |
|         index = -1;
 | |
|       }
 | |
|       if (index == -1) {
 | |
|         this.modules.splice(j++, 0, mod);
 | |
|         if (sheet) for (let k = 0; k < mod.rules.length; k++)
 | |
|           sheet.insertRule(mod.rules[k], pos++);
 | |
|       } else {
 | |
|         while (j < index) pos += this.modules[j++].rules.length;
 | |
|         pos += mod.rules.length;
 | |
|         j++;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (sheet) {
 | |
|       if (root.adoptedStyleSheets.indexOf(this.sheet) < 0)
 | |
|         root.adoptedStyleSheets = [this.sheet, ...root.adoptedStyleSheets];
 | |
|     } else {
 | |
|       let text = "";
 | |
|       for (let i = 0; i < this.modules.length; i++)
 | |
|         text += this.modules[i].getRules() + "\n";
 | |
|       this.styleTag.textContent = text;
 | |
|       let target = root.head || root;
 | |
|       if (this.styleTag.parentNode != target)
 | |
|         target.insertBefore(this.styleTag, target.firstChild);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   setNonce(nonce) {
 | |
|     if (this.styleTag && this.styleTag.getAttribute("nonce") != nonce)
 | |
|       this.styleTag.setAttribute("nonce", nonce);
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Style::Object<union<Style,string>>
 | |
| //
 | |
| // A style is an object that, in the simple case, maps CSS property
 | |
| // names to strings holding their values, as in `{color: "red",
 | |
| // fontWeight: "bold"}`. The property names can be given in
 | |
| // camel-case—the library will insert a dash before capital letters
 | |
| // when converting them to CSS.
 | |
| //
 | |
| // If you include an underscore in a property name, it and everything
 | |
| // after it will be removed from the output, which can be useful when
 | |
| // providing a property multiple times, for browser compatibility
 | |
| // reasons.
 | |
| //
 | |
| // A property in a style object can also be a sub-selector, which
 | |
| // extends the current context to add a pseudo-selector or a child
 | |
| // selector. Such a property should contain a `&` character, which
 | |
| // will be replaced by the current selector. For example `{"&:before":
 | |
| // {content: '"hi"'}}`. Sub-selectors and regular properties can
 | |
| // freely be mixed in a given object. Any property containing a `&` is
 | |
| // assumed to be a sub-selector.
 | |
| //
 | |
| // Finally, a property can specify an @-block to be wrapped around the
 | |
| // styles defined inside the object that's the property's value. For
 | |
| // example to create a media query you can do `{"@media screen and
 | |
| // (min-width: 400px)": {...}}`.
 | |
| 
 | |
| var base = {
 | |
|   8: "Backspace",
 | |
|   9: "Tab",
 | |
|   10: "Enter",
 | |
|   12: "NumLock",
 | |
|   13: "Enter",
 | |
|   16: "Shift",
 | |
|   17: "Control",
 | |
|   18: "Alt",
 | |
|   20: "CapsLock",
 | |
|   27: "Escape",
 | |
|   32: " ",
 | |
|   33: "PageUp",
 | |
|   34: "PageDown",
 | |
|   35: "End",
 | |
|   36: "Home",
 | |
|   37: "ArrowLeft",
 | |
|   38: "ArrowUp",
 | |
|   39: "ArrowRight",
 | |
|   40: "ArrowDown",
 | |
|   44: "PrintScreen",
 | |
|   45: "Insert",
 | |
|   46: "Delete",
 | |
|   59: ";",
 | |
|   61: "=",
 | |
|   91: "Meta",
 | |
|   92: "Meta",
 | |
|   106: "*",
 | |
|   107: "+",
 | |
|   108: ",",
 | |
|   109: "-",
 | |
|   110: ".",
 | |
|   111: "/",
 | |
|   144: "NumLock",
 | |
|   145: "ScrollLock",
 | |
|   160: "Shift",
 | |
|   161: "Shift",
 | |
|   162: "Control",
 | |
|   163: "Control",
 | |
|   164: "Alt",
 | |
|   165: "Alt",
 | |
|   173: "-",
 | |
|   186: ";",
 | |
|   187: "=",
 | |
|   188: ",",
 | |
|   189: "-",
 | |
|   190: ".",
 | |
|   191: "/",
 | |
|   192: "`",
 | |
|   219: "[",
 | |
|   220: "\\",
 | |
|   221: "]",
 | |
|   222: "'"
 | |
| };
 | |
| 
 | |
| var shift = {
 | |
|   48: ")",
 | |
|   49: "!",
 | |
|   50: "@",
 | |
|   51: "#",
 | |
|   52: "$",
 | |
|   53: "%",
 | |
|   54: "^",
 | |
|   55: "&",
 | |
|   56: "*",
 | |
|   57: "(",
 | |
|   59: ":",
 | |
|   61: "+",
 | |
|   173: "_",
 | |
|   186: ":",
 | |
|   187: "+",
 | |
|   188: "<",
 | |
|   189: "_",
 | |
|   190: ">",
 | |
|   191: "?",
 | |
|   192: "~",
 | |
|   219: "{",
 | |
|   220: "|",
 | |
|   221: "}",
 | |
|   222: "\""
 | |
| };
 | |
| 
 | |
| var mac = typeof navigator != "undefined" && /Mac/.test(navigator.platform);
 | |
| var ie$1 = typeof navigator != "undefined" && /MSIE \d|Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent);
 | |
| 
 | |
| // Fill in the digit keys
 | |
| for (var i = 0; i < 10; i++) base[48 + i] = base[96 + i] = String(i);
 | |
| 
 | |
| // The function keys
 | |
| for (var i = 1; i <= 24; i++) base[i + 111] = "F" + i;
 | |
| 
 | |
| // And the alphabetic keys
 | |
| for (var i = 65; i <= 90; i++) {
 | |
|   base[i] = String.fromCharCode(i + 32);
 | |
|   shift[i] = String.fromCharCode(i);
 | |
| }
 | |
| 
 | |
| // For each code that doesn't have a shift-equivalent, copy the base name
 | |
| for (var code in base) if (!shift.hasOwnProperty(code)) shift[code] = base[code];
 | |
| 
 | |
| function keyName(event) {
 | |
|   // On macOS, keys held with Shift and Cmd don't reflect the effect of Shift in `.key`.
 | |
|   // On IE, shift effect is never included in `.key`.
 | |
|   var ignoreKey = mac && event.metaKey && event.shiftKey && !event.ctrlKey && !event.altKey ||
 | |
|       ie$1 && event.shiftKey && event.key && event.key.length == 1 ||
 | |
|       event.key == "Unidentified";
 | |
|   var name = (!ignoreKey && event.key) ||
 | |
|     (event.shiftKey ? shift : base)[event.keyCode] ||
 | |
|     event.key || "Unidentified";
 | |
|   // Edge sometimes produces wrong names (Issue #3)
 | |
|   if (name == "Esc") name = "Escape";
 | |
|   if (name == "Del") name = "Delete";
 | |
|   // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8860571/
 | |
|   if (name == "Left") name = "ArrowLeft";
 | |
|   if (name == "Up") name = "ArrowUp";
 | |
|   if (name == "Right") name = "ArrowRight";
 | |
|   if (name == "Down") name = "ArrowDown";
 | |
|   return name
 | |
| }
 | |
| 
 | |
| function getSelection(root) {
 | |
|     let target;
 | |
|     // Browsers differ on whether shadow roots have a getSelection
 | |
|     // method. If it exists, use that, otherwise, call it on the
 | |
|     // document.
 | |
|     if (root.nodeType == 11) { // Shadow root
 | |
|         target = root.getSelection ? root : root.ownerDocument;
 | |
|     }
 | |
|     else {
 | |
|         target = root;
 | |
|     }
 | |
|     return target.getSelection();
 | |
| }
 | |
| function contains(dom, node) {
 | |
|     return node ? dom == node || dom.contains(node.nodeType != 1 ? node.parentNode : node) : false;
 | |
| }
 | |
| function deepActiveElement(doc) {
 | |
|     let elt = doc.activeElement;
 | |
|     while (elt && elt.shadowRoot)
 | |
|         elt = elt.shadowRoot.activeElement;
 | |
|     return elt;
 | |
| }
 | |
| function hasSelection(dom, selection) {
 | |
|     if (!selection.anchorNode)
 | |
|         return false;
 | |
|     try {
 | |
|         // Firefox will raise 'permission denied' errors when accessing
 | |
|         // properties of `sel.anchorNode` when it's in a generated CSS
 | |
|         // element.
 | |
|         return contains(dom, selection.anchorNode);
 | |
|     }
 | |
|     catch (_) {
 | |
|         return false;
 | |
|     }
 | |
| }
 | |
| function clientRectsFor(dom) {
 | |
|     if (dom.nodeType == 3)
 | |
|         return textRange(dom, 0, dom.nodeValue.length).getClientRects();
 | |
|     else if (dom.nodeType == 1)
 | |
|         return dom.getClientRects();
 | |
|     else
 | |
|         return [];
 | |
| }
 | |
| // Scans forward and backward through DOM positions equivalent to the
 | |
| // given one to see if the two are in the same place (i.e. after a
 | |
| // text node vs at the end of that text node)
 | |
| function isEquivalentPosition(node, off, targetNode, targetOff) {
 | |
|     return targetNode ? (scanFor(node, off, targetNode, targetOff, -1) ||
 | |
|         scanFor(node, off, targetNode, targetOff, 1)) : false;
 | |
| }
 | |
| function domIndex(node) {
 | |
|     for (var index = 0;; index++) {
 | |
|         node = node.previousSibling;
 | |
|         if (!node)
 | |
|             return index;
 | |
|     }
 | |
| }
 | |
| function isBlockElement(node) {
 | |
|     return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
 | |
| }
 | |
| function scanFor(node, off, targetNode, targetOff, dir) {
 | |
|     for (;;) {
 | |
|         if (node == targetNode && off == targetOff)
 | |
|             return true;
 | |
|         if (off == (dir < 0 ? 0 : maxOffset(node))) {
 | |
|             if (node.nodeName == "DIV")
 | |
|                 return false;
 | |
|             let parent = node.parentNode;
 | |
|             if (!parent || parent.nodeType != 1)
 | |
|                 return false;
 | |
|             off = domIndex(node) + (dir < 0 ? 0 : 1);
 | |
|             node = parent;
 | |
|         }
 | |
|         else if (node.nodeType == 1) {
 | |
|             node = node.childNodes[off + (dir < 0 ? -1 : 0)];
 | |
|             if (node.nodeType == 1 && node.contentEditable == "false")
 | |
|                 return false;
 | |
|             off = dir < 0 ? maxOffset(node) : 0;
 | |
|         }
 | |
|         else {
 | |
|             return false;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function maxOffset(node) {
 | |
|     return node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length;
 | |
| }
 | |
| function flattenRect(rect, left) {
 | |
|     let x = left ? rect.left : rect.right;
 | |
|     return { left: x, right: x, top: rect.top, bottom: rect.bottom };
 | |
| }
 | |
| function windowRect(win) {
 | |
|     let vp = win.visualViewport;
 | |
|     if (vp)
 | |
|         return {
 | |
|             left: 0, right: vp.width,
 | |
|             top: 0, bottom: vp.height
 | |
|         };
 | |
|     return { left: 0, right: win.innerWidth,
 | |
|         top: 0, bottom: win.innerHeight };
 | |
| }
 | |
| function getScale(elt, rect) {
 | |
|     let scaleX = rect.width / elt.offsetWidth;
 | |
|     let scaleY = rect.height / elt.offsetHeight;
 | |
|     if (scaleX > 0.995 && scaleX < 1.005 || !isFinite(scaleX) || Math.abs(rect.width - elt.offsetWidth) < 1)
 | |
|         scaleX = 1;
 | |
|     if (scaleY > 0.995 && scaleY < 1.005 || !isFinite(scaleY) || Math.abs(rect.height - elt.offsetHeight) < 1)
 | |
|         scaleY = 1;
 | |
|     return { scaleX, scaleY };
 | |
| }
 | |
| function scrollRectIntoView(dom, rect, side, x, y, xMargin, yMargin, ltr) {
 | |
|     let doc = dom.ownerDocument, win = doc.defaultView || window;
 | |
|     for (let cur = dom, stop = false; cur && !stop;) {
 | |
|         if (cur.nodeType == 1) { // Element
 | |
|             let bounding, top = cur == doc.body;
 | |
|             let scaleX = 1, scaleY = 1;
 | |
|             if (top) {
 | |
|                 bounding = windowRect(win);
 | |
|             }
 | |
|             else {
 | |
|                 if (/^(fixed|sticky)$/.test(getComputedStyle(cur).position))
 | |
|                     stop = true;
 | |
|                 if (cur.scrollHeight <= cur.clientHeight && cur.scrollWidth <= cur.clientWidth) {
 | |
|                     cur = cur.assignedSlot || cur.parentNode;
 | |
|                     continue;
 | |
|                 }
 | |
|                 let rect = cur.getBoundingClientRect();
 | |
|                 ({ scaleX, scaleY } = getScale(cur, rect));
 | |
|                 // Make sure scrollbar width isn't included in the rectangle
 | |
|                 bounding = { left: rect.left, right: rect.left + cur.clientWidth * scaleX,
 | |
|                     top: rect.top, bottom: rect.top + cur.clientHeight * scaleY };
 | |
|             }
 | |
|             let moveX = 0, moveY = 0;
 | |
|             if (y == "nearest") {
 | |
|                 if (rect.top < bounding.top) {
 | |
|                     moveY = -(bounding.top - rect.top + yMargin);
 | |
|                     if (side > 0 && rect.bottom > bounding.bottom + moveY)
 | |
|                         moveY = rect.bottom - bounding.bottom + moveY + yMargin;
 | |
|                 }
 | |
|                 else if (rect.bottom > bounding.bottom) {
 | |
|                     moveY = rect.bottom - bounding.bottom + yMargin;
 | |
|                     if (side < 0 && (rect.top - moveY) < bounding.top)
 | |
|                         moveY = -(bounding.top + moveY - rect.top + yMargin);
 | |
|                 }
 | |
|             }
 | |
|             else {
 | |
|                 let rectHeight = rect.bottom - rect.top, boundingHeight = bounding.bottom - bounding.top;
 | |
|                 let targetTop = y == "center" && rectHeight <= boundingHeight ? rect.top + rectHeight / 2 - boundingHeight / 2 :
 | |
|                     y == "start" || y == "center" && side < 0 ? rect.top - yMargin :
 | |
|                         rect.bottom - boundingHeight + yMargin;
 | |
|                 moveY = targetTop - bounding.top;
 | |
|             }
 | |
|             if (x == "nearest") {
 | |
|                 if (rect.left < bounding.left) {
 | |
|                     moveX = -(bounding.left - rect.left + xMargin);
 | |
|                     if (side > 0 && rect.right > bounding.right + moveX)
 | |
|                         moveX = rect.right - bounding.right + moveX + xMargin;
 | |
|                 }
 | |
|                 else if (rect.right > bounding.right) {
 | |
|                     moveX = rect.right - bounding.right + xMargin;
 | |
|                     if (side < 0 && rect.left < bounding.left + moveX)
 | |
|                         moveX = -(bounding.left + moveX - rect.left + xMargin);
 | |
|                 }
 | |
|             }
 | |
|             else {
 | |
|                 let targetLeft = x == "center" ? rect.left + (rect.right - rect.left) / 2 - (bounding.right - bounding.left) / 2 :
 | |
|                     (x == "start") == ltr ? rect.left - xMargin :
 | |
|                         rect.right - (bounding.right - bounding.left) + xMargin;
 | |
|                 moveX = targetLeft - bounding.left;
 | |
|             }
 | |
|             if (moveX || moveY) {
 | |
|                 if (top) {
 | |
|                     win.scrollBy(moveX, moveY);
 | |
|                 }
 | |
|                 else {
 | |
|                     let movedX = 0, movedY = 0;
 | |
|                     if (moveY) {
 | |
|                         let start = cur.scrollTop;
 | |
|                         cur.scrollTop += moveY / scaleY;
 | |
|                         movedY = (cur.scrollTop - start) * scaleY;
 | |
|                     }
 | |
|                     if (moveX) {
 | |
|                         let start = cur.scrollLeft;
 | |
|                         cur.scrollLeft += moveX / scaleX;
 | |
|                         movedX = (cur.scrollLeft - start) * scaleX;
 | |
|                     }
 | |
|                     rect = { left: rect.left - movedX, top: rect.top - movedY,
 | |
|                         right: rect.right - movedX, bottom: rect.bottom - movedY };
 | |
|                     if (movedX && Math.abs(movedX - moveX) < 1)
 | |
|                         x = "nearest";
 | |
|                     if (movedY && Math.abs(movedY - moveY) < 1)
 | |
|                         y = "nearest";
 | |
|                 }
 | |
|             }
 | |
|             if (top)
 | |
|                 break;
 | |
|             cur = cur.assignedSlot || cur.parentNode;
 | |
|         }
 | |
|         else if (cur.nodeType == 11) { // A shadow root
 | |
|             cur = cur.host;
 | |
|         }
 | |
|         else {
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function scrollableParents(dom) {
 | |
|     let doc = dom.ownerDocument, x, y;
 | |
|     for (let cur = dom.parentNode; cur;) {
 | |
|         if (cur == doc.body || (x && y)) {
 | |
|             break;
 | |
|         }
 | |
|         else if (cur.nodeType == 1) {
 | |
|             if (!y && cur.scrollHeight > cur.clientHeight)
 | |
|                 y = cur;
 | |
|             if (!x && cur.scrollWidth > cur.clientWidth)
 | |
|                 x = cur;
 | |
|             cur = cur.assignedSlot || cur.parentNode;
 | |
|         }
 | |
|         else if (cur.nodeType == 11) {
 | |
|             cur = cur.host;
 | |
|         }
 | |
|         else {
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
|     return { x, y };
 | |
| }
 | |
| class DOMSelectionState {
 | |
|     constructor() {
 | |
|         this.anchorNode = null;
 | |
|         this.anchorOffset = 0;
 | |
|         this.focusNode = null;
 | |
|         this.focusOffset = 0;
 | |
|     }
 | |
|     eq(domSel) {
 | |
|         return this.anchorNode == domSel.anchorNode && this.anchorOffset == domSel.anchorOffset &&
 | |
|             this.focusNode == domSel.focusNode && this.focusOffset == domSel.focusOffset;
 | |
|     }
 | |
|     setRange(range) {
 | |
|         let { anchorNode, focusNode } = range;
 | |
|         // Clip offsets to node size to avoid crashes when Safari reports bogus offsets (#1152)
 | |
|         this.set(anchorNode, Math.min(range.anchorOffset, anchorNode ? maxOffset(anchorNode) : 0), focusNode, Math.min(range.focusOffset, focusNode ? maxOffset(focusNode) : 0));
 | |
|     }
 | |
|     set(anchorNode, anchorOffset, focusNode, focusOffset) {
 | |
|         this.anchorNode = anchorNode;
 | |
|         this.anchorOffset = anchorOffset;
 | |
|         this.focusNode = focusNode;
 | |
|         this.focusOffset = focusOffset;
 | |
|     }
 | |
| }
 | |
| let preventScrollSupported = null;
 | |
| // Feature-detects support for .focus({preventScroll: true}), and uses
 | |
| // a fallback kludge when not supported.
 | |
| function focusPreventScroll(dom) {
 | |
|     if (dom.setActive)
 | |
|         return dom.setActive(); // in IE
 | |
|     if (preventScrollSupported)
 | |
|         return dom.focus(preventScrollSupported);
 | |
|     let stack = [];
 | |
|     for (let cur = dom; cur; cur = cur.parentNode) {
 | |
|         stack.push(cur, cur.scrollTop, cur.scrollLeft);
 | |
|         if (cur == cur.ownerDocument)
 | |
|             break;
 | |
|     }
 | |
|     dom.focus(preventScrollSupported == null ? {
 | |
|         get preventScroll() {
 | |
|             preventScrollSupported = { preventScroll: true };
 | |
|             return true;
 | |
|         }
 | |
|     } : undefined);
 | |
|     if (!preventScrollSupported) {
 | |
|         preventScrollSupported = false;
 | |
|         for (let i = 0; i < stack.length;) {
 | |
|             let elt = stack[i++], top = stack[i++], left = stack[i++];
 | |
|             if (elt.scrollTop != top)
 | |
|                 elt.scrollTop = top;
 | |
|             if (elt.scrollLeft != left)
 | |
|                 elt.scrollLeft = left;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| let scratchRange;
 | |
| function textRange(node, from, to = from) {
 | |
|     let range = scratchRange || (scratchRange = document.createRange());
 | |
|     range.setEnd(node, to);
 | |
|     range.setStart(node, from);
 | |
|     return range;
 | |
| }
 | |
| function dispatchKey(elt, name, code, mods) {
 | |
|     let options = { key: name, code: name, keyCode: code, which: code, cancelable: true };
 | |
|     if (mods)
 | |
|         ({ altKey: options.altKey, ctrlKey: options.ctrlKey, shiftKey: options.shiftKey, metaKey: options.metaKey } = mods);
 | |
|     let down = new KeyboardEvent("keydown", options);
 | |
|     down.synthetic = true;
 | |
|     elt.dispatchEvent(down);
 | |
|     let up = new KeyboardEvent("keyup", options);
 | |
|     up.synthetic = true;
 | |
|     elt.dispatchEvent(up);
 | |
|     return down.defaultPrevented || up.defaultPrevented;
 | |
| }
 | |
| function getRoot(node) {
 | |
|     while (node) {
 | |
|         if (node && (node.nodeType == 9 || node.nodeType == 11 && node.host))
 | |
|             return node;
 | |
|         node = node.assignedSlot || node.parentNode;
 | |
|     }
 | |
|     return null;
 | |
| }
 | |
| function clearAttributes(node) {
 | |
|     while (node.attributes.length)
 | |
|         node.removeAttributeNode(node.attributes[0]);
 | |
| }
 | |
| function atElementStart(doc, selection) {
 | |
|     let node = selection.focusNode, offset = selection.focusOffset;
 | |
|     if (!node || selection.anchorNode != node || selection.anchorOffset != offset)
 | |
|         return false;
 | |
|     // Safari can report bogus offsets (#1152)
 | |
|     offset = Math.min(offset, maxOffset(node));
 | |
|     for (;;) {
 | |
|         if (offset) {
 | |
|             if (node.nodeType != 1)
 | |
|                 return false;
 | |
|             let prev = node.childNodes[offset - 1];
 | |
|             if (prev.contentEditable == "false")
 | |
|                 offset--;
 | |
|             else {
 | |
|                 node = prev;
 | |
|                 offset = maxOffset(node);
 | |
|             }
 | |
|         }
 | |
|         else if (node == doc) {
 | |
|             return true;
 | |
|         }
 | |
|         else {
 | |
|             offset = domIndex(node);
 | |
|             node = node.parentNode;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function isScrolledToBottom(elt) {
 | |
|     return elt.scrollTop > Math.max(1, elt.scrollHeight - elt.clientHeight - 4);
 | |
| }
 | |
| function textNodeBefore(startNode, startOffset) {
 | |
|     for (let node = startNode, offset = startOffset;;) {
 | |
|         if (node.nodeType == 3 && offset > 0) {
 | |
|             return { node: node, offset: offset };
 | |
|         }
 | |
|         else if (node.nodeType == 1 && offset > 0) {
 | |
|             if (node.contentEditable == "false")
 | |
|                 return null;
 | |
|             node = node.childNodes[offset - 1];
 | |
|             offset = maxOffset(node);
 | |
|         }
 | |
|         else if (node.parentNode && !isBlockElement(node)) {
 | |
|             offset = domIndex(node);
 | |
|             node = node.parentNode;
 | |
|         }
 | |
|         else {
 | |
|             return null;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function textNodeAfter(startNode, startOffset) {
 | |
|     for (let node = startNode, offset = startOffset;;) {
 | |
|         if (node.nodeType == 3 && offset < node.nodeValue.length) {
 | |
|             return { node: node, offset: offset };
 | |
|         }
 | |
|         else if (node.nodeType == 1 && offset < node.childNodes.length) {
 | |
|             if (node.contentEditable == "false")
 | |
|                 return null;
 | |
|             node = node.childNodes[offset];
 | |
|             offset = 0;
 | |
|         }
 | |
|         else if (node.parentNode && !isBlockElement(node)) {
 | |
|             offset = domIndex(node) + 1;
 | |
|             node = node.parentNode;
 | |
|         }
 | |
|         else {
 | |
|             return null;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| class DOMPos {
 | |
|     constructor(node, offset, precise = true) {
 | |
|         this.node = node;
 | |
|         this.offset = offset;
 | |
|         this.precise = precise;
 | |
|     }
 | |
|     static before(dom, precise) { return new DOMPos(dom.parentNode, domIndex(dom), precise); }
 | |
|     static after(dom, precise) { return new DOMPos(dom.parentNode, domIndex(dom) + 1, precise); }
 | |
| }
 | |
| const noChildren = [];
 | |
| class ContentView {
 | |
|     constructor() {
 | |
|         this.parent = null;
 | |
|         this.dom = null;
 | |
|         this.flags = 2 /* ViewFlag.NodeDirty */;
 | |
|     }
 | |
|     get overrideDOMText() { return null; }
 | |
|     get posAtStart() {
 | |
|         return this.parent ? this.parent.posBefore(this) : 0;
 | |
|     }
 | |
|     get posAtEnd() {
 | |
|         return this.posAtStart + this.length;
 | |
|     }
 | |
|     posBefore(view) {
 | |
|         let pos = this.posAtStart;
 | |
|         for (let child of this.children) {
 | |
|             if (child == view)
 | |
|                 return pos;
 | |
|             pos += child.length + child.breakAfter;
 | |
|         }
 | |
|         throw new RangeError("Invalid child in posBefore");
 | |
|     }
 | |
|     posAfter(view) {
 | |
|         return this.posBefore(view) + view.length;
 | |
|     }
 | |
|     sync(view, track) {
 | |
|         if (this.flags & 2 /* ViewFlag.NodeDirty */) {
 | |
|             let parent = this.dom;
 | |
|             let prev = null, next;
 | |
|             for (let child of this.children) {
 | |
|                 if (child.flags & 7 /* ViewFlag.Dirty */) {
 | |
|                     if (!child.dom && (next = prev ? prev.nextSibling : parent.firstChild)) {
 | |
|                         let contentView = ContentView.get(next);
 | |
|                         if (!contentView || !contentView.parent && contentView.canReuseDOM(child))
 | |
|                             child.reuseDOM(next);
 | |
|                     }
 | |
|                     child.sync(view, track);
 | |
|                     child.flags &= ~7 /* ViewFlag.Dirty */;
 | |
|                 }
 | |
|                 next = prev ? prev.nextSibling : parent.firstChild;
 | |
|                 if (track && !track.written && track.node == parent && next != child.dom)
 | |
|                     track.written = true;
 | |
|                 if (child.dom.parentNode == parent) {
 | |
|                     while (next && next != child.dom)
 | |
|                         next = rm$1(next);
 | |
|                 }
 | |
|                 else {
 | |
|                     parent.insertBefore(child.dom, next);
 | |
|                 }
 | |
|                 prev = child.dom;
 | |
|             }
 | |
|             next = prev ? prev.nextSibling : parent.firstChild;
 | |
|             if (next && track && track.node == parent)
 | |
|                 track.written = true;
 | |
|             while (next)
 | |
|                 next = rm$1(next);
 | |
|         }
 | |
|         else if (this.flags & 1 /* ViewFlag.ChildDirty */) {
 | |
|             for (let child of this.children)
 | |
|                 if (child.flags & 7 /* ViewFlag.Dirty */) {
 | |
|                     child.sync(view, track);
 | |
|                     child.flags &= ~7 /* ViewFlag.Dirty */;
 | |
|                 }
 | |
|         }
 | |
|     }
 | |
|     reuseDOM(_dom) { }
 | |
|     localPosFromDOM(node, offset) {
 | |
|         let after;
 | |
|         if (node == this.dom) {
 | |
|             after = this.dom.childNodes[offset];
 | |
|         }
 | |
|         else {
 | |
|             let bias = maxOffset(node) == 0 ? 0 : offset == 0 ? -1 : 1;
 | |
|             for (;;) {
 | |
|                 let parent = node.parentNode;
 | |
|                 if (parent == this.dom)
 | |
|                     break;
 | |
|                 if (bias == 0 && parent.firstChild != parent.lastChild) {
 | |
|                     if (node == parent.firstChild)
 | |
|                         bias = -1;
 | |
|                     else
 | |
|                         bias = 1;
 | |
|                 }
 | |
|                 node = parent;
 | |
|             }
 | |
|             if (bias < 0)
 | |
|                 after = node;
 | |
|             else
 | |
|                 after = node.nextSibling;
 | |
|         }
 | |
|         if (after == this.dom.firstChild)
 | |
|             return 0;
 | |
|         while (after && !ContentView.get(after))
 | |
|             after = after.nextSibling;
 | |
|         if (!after)
 | |
|             return this.length;
 | |
|         for (let i = 0, pos = 0;; i++) {
 | |
|             let child = this.children[i];
 | |
|             if (child.dom == after)
 | |
|                 return pos;
 | |
|             pos += child.length + child.breakAfter;
 | |
|         }
 | |
|     }
 | |
|     domBoundsAround(from, to, offset = 0) {
 | |
|         let fromI = -1, fromStart = -1, toI = -1, toEnd = -1;
 | |
|         for (let i = 0, pos = offset, prevEnd = offset; i < this.children.length; i++) {
 | |
|             let child = this.children[i], end = pos + child.length;
 | |
|             if (pos < from && end > to)
 | |
|                 return child.domBoundsAround(from, to, pos);
 | |
|             if (end >= from && fromI == -1) {
 | |
|                 fromI = i;
 | |
|                 fromStart = pos;
 | |
|             }
 | |
|             if (pos > to && child.dom.parentNode == this.dom) {
 | |
|                 toI = i;
 | |
|                 toEnd = prevEnd;
 | |
|                 break;
 | |
|             }
 | |
|             prevEnd = end;
 | |
|             pos = end + child.breakAfter;
 | |
|         }
 | |
|         return { from: fromStart, to: toEnd < 0 ? offset + this.length : toEnd,
 | |
|             startDOM: (fromI ? this.children[fromI - 1].dom.nextSibling : null) || this.dom.firstChild,
 | |
|             endDOM: toI < this.children.length && toI >= 0 ? this.children[toI].dom : null };
 | |
|     }
 | |
|     markDirty(andParent = false) {
 | |
|         this.flags |= 2 /* ViewFlag.NodeDirty */;
 | |
|         this.markParentsDirty(andParent);
 | |
|     }
 | |
|     markParentsDirty(childList) {
 | |
|         for (let parent = this.parent; parent; parent = parent.parent) {
 | |
|             if (childList)
 | |
|                 parent.flags |= 2 /* ViewFlag.NodeDirty */;
 | |
|             if (parent.flags & 1 /* ViewFlag.ChildDirty */)
 | |
|                 return;
 | |
|             parent.flags |= 1 /* ViewFlag.ChildDirty */;
 | |
|             childList = false;
 | |
|         }
 | |
|     }
 | |
|     setParent(parent) {
 | |
|         if (this.parent != parent) {
 | |
|             this.parent = parent;
 | |
|             if (this.flags & 7 /* ViewFlag.Dirty */)
 | |
|                 this.markParentsDirty(true);
 | |
|         }
 | |
|     }
 | |
|     setDOM(dom) {
 | |
|         if (this.dom == dom)
 | |
|             return;
 | |
|         if (this.dom)
 | |
|             this.dom.cmView = null;
 | |
|         this.dom = dom;
 | |
|         dom.cmView = this;
 | |
|     }
 | |
|     get rootView() {
 | |
|         for (let v = this;;) {
 | |
|             let parent = v.parent;
 | |
|             if (!parent)
 | |
|                 return v;
 | |
|             v = parent;
 | |
|         }
 | |
|     }
 | |
|     replaceChildren(from, to, children = noChildren) {
 | |
|         this.markDirty();
 | |
|         for (let i = from; i < to; i++) {
 | |
|             let child = this.children[i];
 | |
|             if (child.parent == this && children.indexOf(child) < 0)
 | |
|                 child.destroy();
 | |
|         }
 | |
|         this.children.splice(from, to - from, ...children);
 | |
|         for (let i = 0; i < children.length; i++)
 | |
|             children[i].setParent(this);
 | |
|     }
 | |
|     ignoreMutation(_rec) { return false; }
 | |
|     ignoreEvent(_event) { return false; }
 | |
|     childCursor(pos = this.length) {
 | |
|         return new ChildCursor(this.children, pos, this.children.length);
 | |
|     }
 | |
|     childPos(pos, bias = 1) {
 | |
|         return this.childCursor().findPos(pos, bias);
 | |
|     }
 | |
|     toString() {
 | |
|         let name = this.constructor.name.replace("View", "");
 | |
|         return name + (this.children.length ? "(" + this.children.join() + ")" :
 | |
|             this.length ? "[" + (name == "Text" ? this.text : this.length) + "]" : "") +
 | |
|             (this.breakAfter ? "#" : "");
 | |
|     }
 | |
|     static get(node) { return node.cmView; }
 | |
|     get isEditable() { return true; }
 | |
|     get isWidget() { return false; }
 | |
|     get isHidden() { return false; }
 | |
|     merge(from, to, source, hasStart, openStart, openEnd) {
 | |
|         return false;
 | |
|     }
 | |
|     become(other) { return false; }
 | |
|     canReuseDOM(other) {
 | |
|         return other.constructor == this.constructor && !((this.flags | other.flags) & 8 /* ViewFlag.Composition */);
 | |
|     }
 | |
|     // When this is a zero-length view with a side, this should return a
 | |
|     // number <= 0 to indicate it is before its position, or a
 | |
|     // number > 0 when after its position.
 | |
|     getSide() { return 0; }
 | |
|     destroy() {
 | |
|         for (let child of this.children)
 | |
|             if (child.parent == this)
 | |
|                 child.destroy();
 | |
|         this.parent = null;
 | |
|     }
 | |
| }
 | |
| ContentView.prototype.breakAfter = 0;
 | |
| // Remove a DOM node and return its next sibling.
 | |
| function rm$1(dom) {
 | |
|     let next = dom.nextSibling;
 | |
|     dom.parentNode.removeChild(dom);
 | |
|     return next;
 | |
| }
 | |
| class ChildCursor {
 | |
|     constructor(children, pos, i) {
 | |
|         this.children = children;
 | |
|         this.pos = pos;
 | |
|         this.i = i;
 | |
|         this.off = 0;
 | |
|     }
 | |
|     findPos(pos, bias = 1) {
 | |
|         for (;;) {
 | |
|             if (pos > this.pos || pos == this.pos &&
 | |
|                 (bias > 0 || this.i == 0 || this.children[this.i - 1].breakAfter)) {
 | |
|                 this.off = pos - this.pos;
 | |
|                 return this;
 | |
|             }
 | |
|             let next = this.children[--this.i];
 | |
|             this.pos -= next.length + next.breakAfter;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function replaceRange(parent, fromI, fromOff, toI, toOff, insert, breakAtStart, openStart, openEnd) {
 | |
|     let { children } = parent;
 | |
|     let before = children.length ? children[fromI] : null;
 | |
|     let last = insert.length ? insert[insert.length - 1] : null;
 | |
|     let breakAtEnd = last ? last.breakAfter : breakAtStart;
 | |
|     // Change within a single child
 | |
|     if (fromI == toI && before && !breakAtStart && !breakAtEnd && insert.length < 2 &&
 | |
|         before.merge(fromOff, toOff, insert.length ? last : null, fromOff == 0, openStart, openEnd))
 | |
|         return;
 | |
|     if (toI < children.length) {
 | |
|         let after = children[toI];
 | |
|         // Make sure the end of the child after the update is preserved in `after`
 | |
|         if (after && (toOff < after.length || after.breakAfter && (last === null || last === void 0 ? void 0 : last.breakAfter))) {
 | |
|             // If we're splitting a child, separate part of it to avoid that
 | |
|             // being mangled when updating the child before the update.
 | |
|             if (fromI == toI) {
 | |
|                 after = after.split(toOff);
 | |
|                 toOff = 0;
 | |
|             }
 | |
|             // If the element after the replacement should be merged with
 | |
|             // the last replacing element, update `content`
 | |
|             if (!breakAtEnd && last && after.merge(0, toOff, last, true, 0, openEnd)) {
 | |
|                 insert[insert.length - 1] = after;
 | |
|             }
 | |
|             else {
 | |
|                 // Remove the start of the after element, if necessary, and
 | |
|                 // add it to `content`.
 | |
|                 if (toOff || after.children.length && !after.children[0].length)
 | |
|                     after.merge(0, toOff, null, false, 0, openEnd);
 | |
|                 insert.push(after);
 | |
|             }
 | |
|         }
 | |
|         else if (after === null || after === void 0 ? void 0 : after.breakAfter) {
 | |
|             // The element at `toI` is entirely covered by this range.
 | |
|             // Preserve its line break, if any.
 | |
|             if (last)
 | |
|                 last.breakAfter = 1;
 | |
|             else
 | |
|                 breakAtStart = 1;
 | |
|         }
 | |
|         // Since we've handled the next element from the current elements
 | |
|         // now, make sure `toI` points after that.
 | |
|         toI++;
 | |
|     }
 | |
|     if (before) {
 | |
|         before.breakAfter = breakAtStart;
 | |
|         if (fromOff > 0) {
 | |
|             if (!breakAtStart && insert.length && before.merge(fromOff, before.length, insert[0], false, openStart, 0)) {
 | |
|                 before.breakAfter = insert.shift().breakAfter;
 | |
|             }
 | |
|             else if (fromOff < before.length || before.children.length && before.children[before.children.length - 1].length == 0) {
 | |
|                 before.merge(fromOff, before.length, null, false, openStart, 0);
 | |
|             }
 | |
|             fromI++;
 | |
|         }
 | |
|     }
 | |
|     // Try to merge widgets on the boundaries of the replacement
 | |
|     while (fromI < toI && insert.length) {
 | |
|         if (children[toI - 1].become(insert[insert.length - 1])) {
 | |
|             toI--;
 | |
|             insert.pop();
 | |
|             openEnd = insert.length ? 0 : openStart;
 | |
|         }
 | |
|         else if (children[fromI].become(insert[0])) {
 | |
|             fromI++;
 | |
|             insert.shift();
 | |
|             openStart = insert.length ? 0 : openEnd;
 | |
|         }
 | |
|         else {
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
|     if (!insert.length && fromI && toI < children.length && !children[fromI - 1].breakAfter &&
 | |
|         children[toI].merge(0, 0, children[fromI - 1], false, openStart, openEnd))
 | |
|         fromI--;
 | |
|     if (fromI < toI || insert.length)
 | |
|         parent.replaceChildren(fromI, toI, insert);
 | |
| }
 | |
| function mergeChildrenInto(parent, from, to, insert, openStart, openEnd) {
 | |
|     let cur = parent.childCursor();
 | |
|     let { i: toI, off: toOff } = cur.findPos(to, 1);
 | |
|     let { i: fromI, off: fromOff } = cur.findPos(from, -1);
 | |
|     let dLen = from - to;
 | |
|     for (let view of insert)
 | |
|         dLen += view.length;
 | |
|     parent.length += dLen;
 | |
|     replaceRange(parent, fromI, fromOff, toI, toOff, insert, 0, openStart, openEnd);
 | |
| }
 | |
| 
 | |
| let nav = typeof navigator != "undefined" ? navigator : { userAgent: "", vendor: "", platform: "" };
 | |
| let doc = typeof document != "undefined" ? document : { documentElement: { style: {} } };
 | |
| const ie_edge = /*@__PURE__*//Edge\/(\d+)/.exec(nav.userAgent);
 | |
| const ie_upto10 = /*@__PURE__*//MSIE \d/.test(nav.userAgent);
 | |
| const ie_11up = /*@__PURE__*//Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(nav.userAgent);
 | |
| const ie = !!(ie_upto10 || ie_11up || ie_edge);
 | |
| const gecko = !ie && /*@__PURE__*//gecko\/(\d+)/i.test(nav.userAgent);
 | |
| const chrome = !ie && /*@__PURE__*//Chrome\/(\d+)/.exec(nav.userAgent);
 | |
| const webkit = "webkitFontSmoothing" in doc.documentElement.style;
 | |
| const safari = !ie && /*@__PURE__*//Apple Computer/.test(nav.vendor);
 | |
| const ios = safari && (/*@__PURE__*//Mobile\/\w+/.test(nav.userAgent) || nav.maxTouchPoints > 2);
 | |
| var browser = {
 | |
|     mac: ios || /*@__PURE__*//Mac/.test(nav.platform),
 | |
|     windows: /*@__PURE__*//Win/.test(nav.platform),
 | |
|     linux: /*@__PURE__*//Linux|X11/.test(nav.platform),
 | |
|     ie,
 | |
|     ie_version: ie_upto10 ? doc.documentMode || 6 : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0,
 | |
|     gecko,
 | |
|     gecko_version: gecko ? +(/*@__PURE__*//Firefox\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
 | |
|     chrome: !!chrome,
 | |
|     chrome_version: chrome ? +chrome[1] : 0,
 | |
|     ios,
 | |
|     android: /*@__PURE__*//Android\b/.test(nav.userAgent),
 | |
|     webkit,
 | |
|     safari,
 | |
|     webkit_version: webkit ? +(/*@__PURE__*//\bAppleWebKit\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
 | |
|     tabSize: doc.documentElement.style.tabSize != null ? "tab-size" : "-moz-tab-size"
 | |
| };
 | |
| 
 | |
| const MaxJoinLen = 256;
 | |
| class TextView extends ContentView {
 | |
|     constructor(text) {
 | |
|         super();
 | |
|         this.text = text;
 | |
|     }
 | |
|     get length() { return this.text.length; }
 | |
|     createDOM(textDOM) {
 | |
|         this.setDOM(textDOM || document.createTextNode(this.text));
 | |
|     }
 | |
|     sync(view, track) {
 | |
|         if (!this.dom)
 | |
|             this.createDOM();
 | |
|         if (this.dom.nodeValue != this.text) {
 | |
|             if (track && track.node == this.dom)
 | |
|                 track.written = true;
 | |
|             this.dom.nodeValue = this.text;
 | |
|         }
 | |
|     }
 | |
|     reuseDOM(dom) {
 | |
|         if (dom.nodeType == 3)
 | |
|             this.createDOM(dom);
 | |
|     }
 | |
|     merge(from, to, source) {
 | |
|         if ((this.flags & 8 /* ViewFlag.Composition */) ||
 | |
|             source && (!(source instanceof TextView) ||
 | |
|                 this.length - (to - from) + source.length > MaxJoinLen ||
 | |
|                 (source.flags & 8 /* ViewFlag.Composition */)))
 | |
|             return false;
 | |
|         this.text = this.text.slice(0, from) + (source ? source.text : "") + this.text.slice(to);
 | |
|         this.markDirty();
 | |
|         return true;
 | |
|     }
 | |
|     split(from) {
 | |
|         let result = new TextView(this.text.slice(from));
 | |
|         this.text = this.text.slice(0, from);
 | |
|         this.markDirty();
 | |
|         result.flags |= this.flags & 8 /* ViewFlag.Composition */;
 | |
|         return result;
 | |
|     }
 | |
|     localPosFromDOM(node, offset) {
 | |
|         return node == this.dom ? offset : offset ? this.text.length : 0;
 | |
|     }
 | |
|     domAtPos(pos) { return new DOMPos(this.dom, pos); }
 | |
|     domBoundsAround(_from, _to, offset) {
 | |
|         return { from: offset, to: offset + this.length, startDOM: this.dom, endDOM: this.dom.nextSibling };
 | |
|     }
 | |
|     coordsAt(pos, side) {
 | |
|         return textCoords(this.dom, pos, side);
 | |
|     }
 | |
| }
 | |
| class MarkView extends ContentView {
 | |
|     constructor(mark, children = [], length = 0) {
 | |
|         super();
 | |
|         this.mark = mark;
 | |
|         this.children = children;
 | |
|         this.length = length;
 | |
|         for (let ch of children)
 | |
|             ch.setParent(this);
 | |
|     }
 | |
|     setAttrs(dom) {
 | |
|         clearAttributes(dom);
 | |
|         if (this.mark.class)
 | |
|             dom.className = this.mark.class;
 | |
|         if (this.mark.attrs)
 | |
|             for (let name in this.mark.attrs)
 | |
|                 dom.setAttribute(name, this.mark.attrs[name]);
 | |
|         return dom;
 | |
|     }
 | |
|     canReuseDOM(other) {
 | |
|         return super.canReuseDOM(other) && !((this.flags | other.flags) & 8 /* ViewFlag.Composition */);
 | |
|     }
 | |
|     reuseDOM(node) {
 | |
|         if (node.nodeName == this.mark.tagName.toUpperCase()) {
 | |
|             this.setDOM(node);
 | |
|             this.flags |= 4 /* ViewFlag.AttrsDirty */ | 2 /* ViewFlag.NodeDirty */;
 | |
|         }
 | |
|     }
 | |
|     sync(view, track) {
 | |
|         if (!this.dom)
 | |
|             this.setDOM(this.setAttrs(document.createElement(this.mark.tagName)));
 | |
|         else if (this.flags & 4 /* ViewFlag.AttrsDirty */)
 | |
|             this.setAttrs(this.dom);
 | |
|         super.sync(view, track);
 | |
|     }
 | |
|     merge(from, to, source, _hasStart, openStart, openEnd) {
 | |
|         if (source && (!(source instanceof MarkView && source.mark.eq(this.mark)) ||
 | |
|             (from && openStart <= 0) || (to < this.length && openEnd <= 0)))
 | |
|             return false;
 | |
|         mergeChildrenInto(this, from, to, source ? source.children.slice() : [], openStart - 1, openEnd - 1);
 | |
|         this.markDirty();
 | |
|         return true;
 | |
|     }
 | |
|     split(from) {
 | |
|         let result = [], off = 0, detachFrom = -1, i = 0;
 | |
|         for (let elt of this.children) {
 | |
|             let end = off + elt.length;
 | |
|             if (end > from)
 | |
|                 result.push(off < from ? elt.split(from - off) : elt);
 | |
|             if (detachFrom < 0 && off >= from)
 | |
|                 detachFrom = i;
 | |
|             off = end;
 | |
|             i++;
 | |
|         }
 | |
|         let length = this.length - from;
 | |
|         this.length = from;
 | |
|         if (detachFrom > -1) {
 | |
|             this.children.length = detachFrom;
 | |
|             this.markDirty();
 | |
|         }
 | |
|         return new MarkView(this.mark, result, length);
 | |
|     }
 | |
|     domAtPos(pos) {
 | |
|         return inlineDOMAtPos(this, pos);
 | |
|     }
 | |
|     coordsAt(pos, side) {
 | |
|         return coordsInChildren(this, pos, side);
 | |
|     }
 | |
| }
 | |
| function textCoords(text, pos, side) {
 | |
|     let length = text.nodeValue.length;
 | |
|     if (pos > length)
 | |
|         pos = length;
 | |
|     let from = pos, to = pos, flatten = 0;
 | |
|     if (pos == 0 && side < 0 || pos == length && side >= 0) {
 | |
|         if (!(browser.chrome || browser.gecko)) { // These browsers reliably return valid rectangles for empty ranges
 | |
|             if (pos) {
 | |
|                 from--;
 | |
|                 flatten = 1;
 | |
|             } // FIXME this is wrong in RTL text
 | |
|             else if (to < length) {
 | |
|                 to++;
 | |
|                 flatten = -1;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     else {
 | |
|         if (side < 0)
 | |
|             from--;
 | |
|         else if (to < length)
 | |
|             to++;
 | |
|     }
 | |
|     let rects = textRange(text, from, to).getClientRects();
 | |
|     if (!rects.length)
 | |
|         return null;
 | |
|     let rect = rects[(flatten ? flatten < 0 : side >= 0) ? 0 : rects.length - 1];
 | |
|     if (browser.safari && !flatten && rect.width == 0)
 | |
|         rect = Array.prototype.find.call(rects, r => r.width) || rect;
 | |
|     return flatten ? flattenRect(rect, flatten < 0) : rect || null;
 | |
| }
 | |
| // Also used for collapsed ranges that don't have a placeholder widget!
 | |
| class WidgetView extends ContentView {
 | |
|     static create(widget, length, side) {
 | |
|         return new WidgetView(widget, length, side);
 | |
|     }
 | |
|     constructor(widget, length, side) {
 | |
|         super();
 | |
|         this.widget = widget;
 | |
|         this.length = length;
 | |
|         this.side = side;
 | |
|         this.prevWidget = null;
 | |
|     }
 | |
|     split(from) {
 | |
|         let result = WidgetView.create(this.widget, this.length - from, this.side);
 | |
|         this.length -= from;
 | |
|         return result;
 | |
|     }
 | |
|     sync(view) {
 | |
|         if (!this.dom || !this.widget.updateDOM(this.dom, view)) {
 | |
|             if (this.dom && this.prevWidget)
 | |
|                 this.prevWidget.destroy(this.dom);
 | |
|             this.prevWidget = null;
 | |
|             this.setDOM(this.widget.toDOM(view));
 | |
|             if (!this.widget.editable)
 | |
|                 this.dom.contentEditable = "false";
 | |
|         }
 | |
|     }
 | |
|     getSide() { return this.side; }
 | |
|     merge(from, to, source, hasStart, openStart, openEnd) {
 | |
|         if (source && (!(source instanceof WidgetView) || !this.widget.compare(source.widget) ||
 | |
|             from > 0 && openStart <= 0 || to < this.length && openEnd <= 0))
 | |
|             return false;
 | |
|         this.length = from + (source ? source.length : 0) + (this.length - to);
 | |
|         return true;
 | |
|     }
 | |
|     become(other) {
 | |
|         if (other instanceof WidgetView && other.side == this.side &&
 | |
|             this.widget.constructor == other.widget.constructor) {
 | |
|             if (!this.widget.compare(other.widget))
 | |
|                 this.markDirty(true);
 | |
|             if (this.dom && !this.prevWidget)
 | |
|                 this.prevWidget = this.widget;
 | |
|             this.widget = other.widget;
 | |
|             this.length = other.length;
 | |
|             return true;
 | |
|         }
 | |
|         return false;
 | |
|     }
 | |
|     ignoreMutation() { return true; }
 | |
|     ignoreEvent(event) { return this.widget.ignoreEvent(event); }
 | |
|     get overrideDOMText() {
 | |
|         if (this.length == 0)
 | |
|             return Text.empty;
 | |
|         let top = this;
 | |
|         while (top.parent)
 | |
|             top = top.parent;
 | |
|         let { view } = top, text = view && view.state.doc, start = this.posAtStart;
 | |
|         return text ? text.slice(start, start + this.length) : Text.empty;
 | |
|     }
 | |
|     domAtPos(pos) {
 | |
|         return (this.length ? pos == 0 : this.side > 0)
 | |
|             ? DOMPos.before(this.dom)
 | |
|             : DOMPos.after(this.dom, pos == this.length);
 | |
|     }
 | |
|     domBoundsAround() { return null; }
 | |
|     coordsAt(pos, side) {
 | |
|         let custom = this.widget.coordsAt(this.dom, pos, side);
 | |
|         if (custom)
 | |
|             return custom;
 | |
|         let rects = this.dom.getClientRects(), rect = null;
 | |
|         if (!rects.length)
 | |
|             return null;
 | |
|         let fromBack = this.side ? this.side < 0 : pos > 0;
 | |
|         for (let i = fromBack ? rects.length - 1 : 0;; i += (fromBack ? -1 : 1)) {
 | |
|             rect = rects[i];
 | |
|             if (pos > 0 ? i == 0 : i == rects.length - 1 || rect.top < rect.bottom)
 | |
|                 break;
 | |
|         }
 | |
|         return flattenRect(rect, !fromBack);
 | |
|     }
 | |
|     get isEditable() { return false; }
 | |
|     get isWidget() { return true; }
 | |
|     get isHidden() { return this.widget.isHidden; }
 | |
|     destroy() {
 | |
|         super.destroy();
 | |
|         if (this.dom)
 | |
|             this.widget.destroy(this.dom);
 | |
|     }
 | |
| }
 | |
| // These are drawn around uneditable widgets to avoid a number of
 | |
| // browser bugs that show up when the cursor is directly next to
 | |
| // uneditable inline content.
 | |
| class WidgetBufferView extends ContentView {
 | |
|     constructor(side) {
 | |
|         super();
 | |
|         this.side = side;
 | |
|     }
 | |
|     get length() { return 0; }
 | |
|     merge() { return false; }
 | |
|     become(other) {
 | |
|         return other instanceof WidgetBufferView && other.side == this.side;
 | |
|     }
 | |
|     split() { return new WidgetBufferView(this.side); }
 | |
|     sync() {
 | |
|         if (!this.dom) {
 | |
|             let dom = document.createElement("img");
 | |
|             dom.className = "cm-widgetBuffer";
 | |
|             dom.setAttribute("aria-hidden", "true");
 | |
|             this.setDOM(dom);
 | |
|         }
 | |
|     }
 | |
|     getSide() { return this.side; }
 | |
|     domAtPos(pos) { return this.side > 0 ? DOMPos.before(this.dom) : DOMPos.after(this.dom); }
 | |
|     localPosFromDOM() { return 0; }
 | |
|     domBoundsAround() { return null; }
 | |
|     coordsAt(pos) {
 | |
|         return this.dom.getBoundingClientRect();
 | |
|     }
 | |
|     get overrideDOMText() {
 | |
|         return Text.empty;
 | |
|     }
 | |
|     get isHidden() { return true; }
 | |
| }
 | |
| TextView.prototype.children = WidgetView.prototype.children = WidgetBufferView.prototype.children = noChildren;
 | |
| function inlineDOMAtPos(parent, pos) {
 | |
|     let dom = parent.dom, { children } = parent, i = 0;
 | |
|     for (let off = 0; i < children.length; i++) {
 | |
|         let child = children[i], end = off + child.length;
 | |
|         if (end == off && child.getSide() <= 0)
 | |
|             continue;
 | |
|         if (pos > off && pos < end && child.dom.parentNode == dom)
 | |
|             return child.domAtPos(pos - off);
 | |
|         if (pos <= off)
 | |
|             break;
 | |
|         off = end;
 | |
|     }
 | |
|     for (let j = i; j > 0; j--) {
 | |
|         let prev = children[j - 1];
 | |
|         if (prev.dom.parentNode == dom)
 | |
|             return prev.domAtPos(prev.length);
 | |
|     }
 | |
|     for (let j = i; j < children.length; j++) {
 | |
|         let next = children[j];
 | |
|         if (next.dom.parentNode == dom)
 | |
|             return next.domAtPos(0);
 | |
|     }
 | |
|     return new DOMPos(dom, 0);
 | |
| }
 | |
| // Assumes `view`, if a mark view, has precisely 1 child.
 | |
| function joinInlineInto(parent, view, open) {
 | |
|     let last, { children } = parent;
 | |
|     if (open > 0 && view instanceof MarkView && children.length &&
 | |
|         (last = children[children.length - 1]) instanceof MarkView && last.mark.eq(view.mark)) {
 | |
|         joinInlineInto(last, view.children[0], open - 1);
 | |
|     }
 | |
|     else {
 | |
|         children.push(view);
 | |
|         view.setParent(parent);
 | |
|     }
 | |
|     parent.length += view.length;
 | |
| }
 | |
| function coordsInChildren(view, pos, side) {
 | |
|     let before = null, beforePos = -1, after = null, afterPos = -1;
 | |
|     function scan(view, pos) {
 | |
|         for (let i = 0, off = 0; i < view.children.length && off <= pos; i++) {
 | |
|             let child = view.children[i], end = off + child.length;
 | |
|             if (end >= pos) {
 | |
|                 if (child.children.length) {
 | |
|                     scan(child, pos - off);
 | |
|                 }
 | |
|                 else if ((!after || after.isHidden && side > 0) &&
 | |
|                     (end > pos || off == end && child.getSide() > 0)) {
 | |
|                     after = child;
 | |
|                     afterPos = pos - off;
 | |
|                 }
 | |
|                 else if (off < pos || (off == end && child.getSide() < 0) && !child.isHidden) {
 | |
|                     before = child;
 | |
|                     beforePos = pos - off;
 | |
|                 }
 | |
|             }
 | |
|             off = end;
 | |
|         }
 | |
|     }
 | |
|     scan(view, pos);
 | |
|     let target = (side < 0 ? before : after) || before || after;
 | |
|     if (target)
 | |
|         return target.coordsAt(Math.max(0, target == before ? beforePos : afterPos), side);
 | |
|     return fallbackRect(view);
 | |
| }
 | |
| function fallbackRect(view) {
 | |
|     let last = view.dom.lastChild;
 | |
|     if (!last)
 | |
|         return view.dom.getBoundingClientRect();
 | |
|     let rects = clientRectsFor(last);
 | |
|     return rects[rects.length - 1] || null;
 | |
| }
 | |
| 
 | |
| function combineAttrs(source, target) {
 | |
|     for (let name in source) {
 | |
|         if (name == "class" && target.class)
 | |
|             target.class += " " + source.class;
 | |
|         else if (name == "style" && target.style)
 | |
|             target.style += ";" + source.style;
 | |
|         else
 | |
|             target[name] = source[name];
 | |
|     }
 | |
|     return target;
 | |
| }
 | |
| const noAttrs = /*@__PURE__*/Object.create(null);
 | |
| function attrsEq(a, b, ignore) {
 | |
|     if (a == b)
 | |
|         return true;
 | |
|     if (!a)
 | |
|         a = noAttrs;
 | |
|     if (!b)
 | |
|         b = noAttrs;
 | |
|     let keysA = Object.keys(a), keysB = Object.keys(b);
 | |
|     if (keysA.length - (ignore && keysA.indexOf(ignore) > -1 ? 1 : 0) !=
 | |
|         keysB.length - (ignore && keysB.indexOf(ignore) > -1 ? 1 : 0))
 | |
|         return false;
 | |
|     for (let key of keysA) {
 | |
|         if (key != ignore && (keysB.indexOf(key) == -1 || a[key] !== b[key]))
 | |
|             return false;
 | |
|     }
 | |
|     return true;
 | |
| }
 | |
| function updateAttrs(dom, prev, attrs) {
 | |
|     let changed = false;
 | |
|     if (prev)
 | |
|         for (let name in prev)
 | |
|             if (!(attrs && name in attrs)) {
 | |
|                 changed = true;
 | |
|                 if (name == "style")
 | |
|                     dom.style.cssText = "";
 | |
|                 else
 | |
|                     dom.removeAttribute(name);
 | |
|             }
 | |
|     if (attrs)
 | |
|         for (let name in attrs)
 | |
|             if (!(prev && prev[name] == attrs[name])) {
 | |
|                 changed = true;
 | |
|                 if (name == "style")
 | |
|                     dom.style.cssText = attrs[name];
 | |
|                 else
 | |
|                     dom.setAttribute(name, attrs[name]);
 | |
|             }
 | |
|     return changed;
 | |
| }
 | |
| function getAttrs(dom) {
 | |
|     let attrs = Object.create(null);
 | |
|     for (let i = 0; i < dom.attributes.length; i++) {
 | |
|         let attr = dom.attributes[i];
 | |
|         attrs[attr.name] = attr.value;
 | |
|     }
 | |
|     return attrs;
 | |
| }
 | |
| 
 | |
| /**
 | |
| Widgets added to the content are described by subclasses of this
 | |
| class. Using a description object like that makes it possible to
 | |
| delay creating of the DOM structure for a widget until it is
 | |
| needed, and to avoid redrawing widgets even if the decorations
 | |
| that define them are recreated.
 | |
| */
 | |
| class WidgetType {
 | |
|     /**
 | |
|     Compare this instance to another instance of the same type.
 | |
|     (TypeScript can't express this, but only instances of the same
 | |
|     specific class will be passed to this method.) This is used to
 | |
|     avoid redrawing widgets when they are replaced by a new
 | |
|     decoration of the same type. The default implementation just
 | |
|     returns `false`, which will cause new instances of the widget to
 | |
|     always be redrawn.
 | |
|     */
 | |
|     eq(widget) { return false; }
 | |
|     /**
 | |
|     Update a DOM element created by a widget of the same type (but
 | |
|     different, non-`eq` content) to reflect this widget. May return
 | |
|     true to indicate that it could update, false to indicate it
 | |
|     couldn't (in which case the widget will be redrawn). The default
 | |
|     implementation just returns false.
 | |
|     */
 | |
|     updateDOM(dom, view) { return false; }
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     compare(other) {
 | |
|         return this == other || this.constructor == other.constructor && this.eq(other);
 | |
|     }
 | |
|     /**
 | |
|     The estimated height this widget will have, to be used when
 | |
|     estimating the height of content that hasn't been drawn. May
 | |
|     return -1 to indicate you don't know. The default implementation
 | |
|     returns -1.
 | |
|     */
 | |
|     get estimatedHeight() { return -1; }
 | |
|     /**
 | |
|     For inline widgets that are displayed inline (as opposed to
 | |
|     `inline-block`) and introduce line breaks (through `<br>` tags
 | |
|     or textual newlines), this must indicate the amount of line
 | |
|     breaks they introduce. Defaults to 0.
 | |
|     */
 | |
|     get lineBreaks() { return 0; }
 | |
|     /**
 | |
|     Can be used to configure which kinds of events inside the widget
 | |
|     should be ignored by the editor. The default is to ignore all
 | |
|     events.
 | |
|     */
 | |
|     ignoreEvent(event) { return true; }
 | |
|     /**
 | |
|     Override the way screen coordinates for positions at/in the
 | |
|     widget are found. `pos` will be the offset into the widget, and
 | |
|     `side` the side of the position that is being queried—less than
 | |
|     zero for before, greater than zero for after, and zero for
 | |
|     directly at that position.
 | |
|     */
 | |
|     coordsAt(dom, pos, side) { return null; }
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     get isHidden() { return false; }
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     get editable() { return false; }
 | |
|     /**
 | |
|     This is called when the an instance of the widget is removed
 | |
|     from the editor view.
 | |
|     */
 | |
|     destroy(dom) { }
 | |
| }
 | |
| /**
 | |
| The different types of blocks that can occur in an editor view.
 | |
| */
 | |
| var BlockType = /*@__PURE__*/(function (BlockType) {
 | |
|     /**
 | |
|     A line of text.
 | |
|     */
 | |
|     BlockType[BlockType["Text"] = 0] = "Text";
 | |
|     /**
 | |
|     A block widget associated with the position after it.
 | |
|     */
 | |
|     BlockType[BlockType["WidgetBefore"] = 1] = "WidgetBefore";
 | |
|     /**
 | |
|     A block widget associated with the position before it.
 | |
|     */
 | |
|     BlockType[BlockType["WidgetAfter"] = 2] = "WidgetAfter";
 | |
|     /**
 | |
|     A block widget [replacing](https://codemirror.net/6/docs/ref/#view.Decoration^replace) a range of content.
 | |
|     */
 | |
|     BlockType[BlockType["WidgetRange"] = 3] = "WidgetRange";
 | |
| return BlockType})(BlockType || (BlockType = {}));
 | |
| /**
 | |
| A decoration provides information on how to draw or style a piece
 | |
| of content. You'll usually use it wrapped in a
 | |
| [`Range`](https://codemirror.net/6/docs/ref/#state.Range), which adds a start and end position.
 | |
| @nonabstract
 | |
| */
 | |
| class Decoration extends RangeValue {
 | |
|     constructor(
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     startSide, 
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     endSide, 
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     widget, 
 | |
|     /**
 | |
|     The config object used to create this decoration. You can
 | |
|     include additional properties in there to store metadata about
 | |
|     your decoration.
 | |
|     */
 | |
|     spec) {
 | |
|         super();
 | |
|         this.startSide = startSide;
 | |
|         this.endSide = endSide;
 | |
|         this.widget = widget;
 | |
|         this.spec = spec;
 | |
|     }
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     get heightRelevant() { return false; }
 | |
|     /**
 | |
|     Create a mark decoration, which influences the styling of the
 | |
|     content in its range. Nested mark decorations will cause nested
 | |
|     DOM elements to be created. Nesting order is determined by
 | |
|     precedence of the [facet](https://codemirror.net/6/docs/ref/#view.EditorView^decorations), with
 | |
|     the higher-precedence decorations creating the inner DOM nodes.
 | |
|     Such elements are split on line boundaries and on the boundaries
 | |
|     of lower-precedence decorations.
 | |
|     */
 | |
|     static mark(spec) {
 | |
|         return new MarkDecoration(spec);
 | |
|     }
 | |
|     /**
 | |
|     Create a widget decoration, which displays a DOM element at the
 | |
|     given position.
 | |
|     */
 | |
|     static widget(spec) {
 | |
|         let side = Math.max(-10000, Math.min(10000, spec.side || 0)), block = !!spec.block;
 | |
|         side += (block && !spec.inlineOrder)
 | |
|             ? (side > 0 ? 300000000 /* Side.BlockAfter */ : -400000000 /* Side.BlockBefore */)
 | |
|             : (side > 0 ? 100000000 /* Side.InlineAfter */ : -100000000 /* Side.InlineBefore */);
 | |
|         return new PointDecoration(spec, side, side, block, spec.widget || null, false);
 | |
|     }
 | |
|     /**
 | |
|     Create a replace decoration which replaces the given range with
 | |
|     a widget, or simply hides it.
 | |
|     */
 | |
|     static replace(spec) {
 | |
|         let block = !!spec.block, startSide, endSide;
 | |
|         if (spec.isBlockGap) {
 | |
|             startSide = -500000000 /* Side.GapStart */;
 | |
|             endSide = 400000000 /* Side.GapEnd */;
 | |
|         }
 | |
|         else {
 | |
|             let { start, end } = getInclusive(spec, block);
 | |
|             startSide = (start ? (block ? -300000000 /* Side.BlockIncStart */ : -1 /* Side.InlineIncStart */) : 500000000 /* Side.NonIncStart */) - 1;
 | |
|             endSide = (end ? (block ? 200000000 /* Side.BlockIncEnd */ : 1 /* Side.InlineIncEnd */) : -600000000 /* Side.NonIncEnd */) + 1;
 | |
|         }
 | |
|         return new PointDecoration(spec, startSide, endSide, block, spec.widget || null, true);
 | |
|     }
 | |
|     /**
 | |
|     Create a line decoration, which can add DOM attributes to the
 | |
|     line starting at the given position.
 | |
|     */
 | |
|     static line(spec) {
 | |
|         return new LineDecoration(spec);
 | |
|     }
 | |
|     /**
 | |
|     Build a [`DecorationSet`](https://codemirror.net/6/docs/ref/#view.DecorationSet) from the given
 | |
|     decorated range or ranges. If the ranges aren't already sorted,
 | |
|     pass `true` for `sort` to make the library sort them for you.
 | |
|     */
 | |
|     static set(of, sort = false) {
 | |
|         return RangeSet.of(of, sort);
 | |
|     }
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     hasHeight() { return this.widget ? this.widget.estimatedHeight > -1 : false; }
 | |
| }
 | |
| /**
 | |
| The empty set of decorations.
 | |
| */
 | |
| Decoration.none = RangeSet.empty;
 | |
| class MarkDecoration extends Decoration {
 | |
|     constructor(spec) {
 | |
|         let { start, end } = getInclusive(spec);
 | |
|         super(start ? -1 /* Side.InlineIncStart */ : 500000000 /* Side.NonIncStart */, end ? 1 /* Side.InlineIncEnd */ : -600000000 /* Side.NonIncEnd */, null, spec);
 | |
|         this.tagName = spec.tagName || "span";
 | |
|         this.class = spec.class || "";
 | |
|         this.attrs = spec.attributes || null;
 | |
|     }
 | |
|     eq(other) {
 | |
|         var _a, _b;
 | |
|         return this == other ||
 | |
|             other instanceof MarkDecoration &&
 | |
|                 this.tagName == other.tagName &&
 | |
|                 (this.class || ((_a = this.attrs) === null || _a === void 0 ? void 0 : _a.class)) == (other.class || ((_b = other.attrs) === null || _b === void 0 ? void 0 : _b.class)) &&
 | |
|                 attrsEq(this.attrs, other.attrs, "class");
 | |
|     }
 | |
|     range(from, to = from) {
 | |
|         if (from >= to)
 | |
|             throw new RangeError("Mark decorations may not be empty");
 | |
|         return super.range(from, to);
 | |
|     }
 | |
| }
 | |
| MarkDecoration.prototype.point = false;
 | |
| class LineDecoration extends Decoration {
 | |
|     constructor(spec) {
 | |
|         super(-200000000 /* Side.Line */, -200000000 /* Side.Line */, null, spec);
 | |
|     }
 | |
|     eq(other) {
 | |
|         return other instanceof LineDecoration &&
 | |
|             this.spec.class == other.spec.class &&
 | |
|             attrsEq(this.spec.attributes, other.spec.attributes);
 | |
|     }
 | |
|     range(from, to = from) {
 | |
|         if (to != from)
 | |
|             throw new RangeError("Line decoration ranges must be zero-length");
 | |
|         return super.range(from, to);
 | |
|     }
 | |
| }
 | |
| LineDecoration.prototype.mapMode = MapMode.TrackBefore;
 | |
| LineDecoration.prototype.point = true;
 | |
| class PointDecoration extends Decoration {
 | |
|     constructor(spec, startSide, endSide, block, widget, isReplace) {
 | |
|         super(startSide, endSide, widget, spec);
 | |
|         this.block = block;
 | |
|         this.isReplace = isReplace;
 | |
|         this.mapMode = !block ? MapMode.TrackDel : startSide <= 0 ? MapMode.TrackBefore : MapMode.TrackAfter;
 | |
|     }
 | |
|     // Only relevant when this.block == true
 | |
|     get type() {
 | |
|         return this.startSide != this.endSide ? BlockType.WidgetRange
 | |
|             : this.startSide <= 0 ? BlockType.WidgetBefore : BlockType.WidgetAfter;
 | |
|     }
 | |
|     get heightRelevant() {
 | |
|         return this.block || !!this.widget && (this.widget.estimatedHeight >= 5 || this.widget.lineBreaks > 0);
 | |
|     }
 | |
|     eq(other) {
 | |
|         return other instanceof PointDecoration &&
 | |
|             widgetsEq(this.widget, other.widget) &&
 | |
|             this.block == other.block &&
 | |
|             this.startSide == other.startSide && this.endSide == other.endSide;
 | |
|     }
 | |
|     range(from, to = from) {
 | |
|         if (this.isReplace && (from > to || (from == to && this.startSide > 0 && this.endSide <= 0)))
 | |
|             throw new RangeError("Invalid range for replacement decoration");
 | |
|         if (!this.isReplace && to != from)
 | |
|             throw new RangeError("Widget decorations can only have zero-length ranges");
 | |
|         return super.range(from, to);
 | |
|     }
 | |
| }
 | |
| PointDecoration.prototype.point = true;
 | |
| function getInclusive(spec, block = false) {
 | |
|     let { inclusiveStart: start, inclusiveEnd: end } = spec;
 | |
|     if (start == null)
 | |
|         start = spec.inclusive;
 | |
|     if (end == null)
 | |
|         end = spec.inclusive;
 | |
|     return { start: start !== null && start !== void 0 ? start : block, end: end !== null && end !== void 0 ? end : block };
 | |
| }
 | |
| function widgetsEq(a, b) {
 | |
|     return a == b || !!(a && b && a.compare(b));
 | |
| }
 | |
| function addRange(from, to, ranges, margin = 0) {
 | |
|     let last = ranges.length - 1;
 | |
|     if (last >= 0 && ranges[last] + margin >= from)
 | |
|         ranges[last] = Math.max(ranges[last], to);
 | |
|     else
 | |
|         ranges.push(from, to);
 | |
| }
 | |
| 
 | |
| class LineView extends ContentView {
 | |
|     constructor() {
 | |
|         super(...arguments);
 | |
|         this.children = [];
 | |
|         this.length = 0;
 | |
|         this.prevAttrs = undefined;
 | |
|         this.attrs = null;
 | |
|         this.breakAfter = 0;
 | |
|     }
 | |
|     // Consumes source
 | |
|     merge(from, to, source, hasStart, openStart, openEnd) {
 | |
|         if (source) {
 | |
|             if (!(source instanceof LineView))
 | |
|                 return false;
 | |
|             if (!this.dom)
 | |
|                 source.transferDOM(this); // Reuse source.dom when appropriate
 | |
|         }
 | |
|         if (hasStart)
 | |
|             this.setDeco(source ? source.attrs : null);
 | |
|         mergeChildrenInto(this, from, to, source ? source.children.slice() : [], openStart, openEnd);
 | |
|         return true;
 | |
|     }
 | |
|     split(at) {
 | |
|         let end = new LineView;
 | |
|         end.breakAfter = this.breakAfter;
 | |
|         if (this.length == 0)
 | |
|             return end;
 | |
|         let { i, off } = this.childPos(at);
 | |
|         if (off) {
 | |
|             end.append(this.children[i].split(off), 0);
 | |
|             this.children[i].merge(off, this.children[i].length, null, false, 0, 0);
 | |
|             i++;
 | |
|         }
 | |
|         for (let j = i; j < this.children.length; j++)
 | |
|             end.append(this.children[j], 0);
 | |
|         while (i > 0 && this.children[i - 1].length == 0)
 | |
|             this.children[--i].destroy();
 | |
|         this.children.length = i;
 | |
|         this.markDirty();
 | |
|         this.length = at;
 | |
|         return end;
 | |
|     }
 | |
|     transferDOM(other) {
 | |
|         if (!this.dom)
 | |
|             return;
 | |
|         this.markDirty();
 | |
|         other.setDOM(this.dom);
 | |
|         other.prevAttrs = this.prevAttrs === undefined ? this.attrs : this.prevAttrs;
 | |
|         this.prevAttrs = undefined;
 | |
|         this.dom = null;
 | |
|     }
 | |
|     setDeco(attrs) {
 | |
|         if (!attrsEq(this.attrs, attrs)) {
 | |
|             if (this.dom) {
 | |
|                 this.prevAttrs = this.attrs;
 | |
|                 this.markDirty();
 | |
|             }
 | |
|             this.attrs = attrs;
 | |
|         }
 | |
|     }
 | |
|     append(child, openStart) {
 | |
|         joinInlineInto(this, child, openStart);
 | |
|     }
 | |
|     // Only called when building a line view in ContentBuilder
 | |
|     addLineDeco(deco) {
 | |
|         let attrs = deco.spec.attributes, cls = deco.spec.class;
 | |
|         if (attrs)
 | |
|             this.attrs = combineAttrs(attrs, this.attrs || {});
 | |
|         if (cls)
 | |
|             this.attrs = combineAttrs({ class: cls }, this.attrs || {});
 | |
|     }
 | |
|     domAtPos(pos) {
 | |
|         return inlineDOMAtPos(this, pos);
 | |
|     }
 | |
|     reuseDOM(node) {
 | |
|         if (node.nodeName == "DIV") {
 | |
|             this.setDOM(node);
 | |
|             this.flags |= 4 /* ViewFlag.AttrsDirty */ | 2 /* ViewFlag.NodeDirty */;
 | |
|         }
 | |
|     }
 | |
|     sync(view, track) {
 | |
|         var _a;
 | |
|         if (!this.dom) {
 | |
|             this.setDOM(document.createElement("div"));
 | |
|             this.dom.className = "cm-line";
 | |
|             this.prevAttrs = this.attrs ? null : undefined;
 | |
|         }
 | |
|         else if (this.flags & 4 /* ViewFlag.AttrsDirty */) {
 | |
|             clearAttributes(this.dom);
 | |
|             this.dom.className = "cm-line";
 | |
|             this.prevAttrs = this.attrs ? null : undefined;
 | |
|         }
 | |
|         if (this.prevAttrs !== undefined) {
 | |
|             updateAttrs(this.dom, this.prevAttrs, this.attrs);
 | |
|             this.dom.classList.add("cm-line");
 | |
|             this.prevAttrs = undefined;
 | |
|         }
 | |
|         super.sync(view, track);
 | |
|         let last = this.dom.lastChild;
 | |
|         while (last && ContentView.get(last) instanceof MarkView)
 | |
|             last = last.lastChild;
 | |
|         if (!last || !this.length ||
 | |
|             last.nodeName != "BR" && ((_a = ContentView.get(last)) === null || _a === void 0 ? void 0 : _a.isEditable) == false &&
 | |
|                 (!browser.ios || !this.children.some(ch => ch instanceof TextView))) {
 | |
|             let hack = document.createElement("BR");
 | |
|             hack.cmIgnore = true;
 | |
|             this.dom.appendChild(hack);
 | |
|         }
 | |
|     }
 | |
|     measureTextSize() {
 | |
|         if (this.children.length == 0 || this.length > 20)
 | |
|             return null;
 | |
|         let totalWidth = 0, textHeight;
 | |
|         for (let child of this.children) {
 | |
|             if (!(child instanceof TextView) || /[^ -~]/.test(child.text))
 | |
|                 return null;
 | |
|             let rects = clientRectsFor(child.dom);
 | |
|             if (rects.length != 1)
 | |
|                 return null;
 | |
|             totalWidth += rects[0].width;
 | |
|             textHeight = rects[0].height;
 | |
|         }
 | |
|         return !totalWidth ? null : {
 | |
|             lineHeight: this.dom.getBoundingClientRect().height,
 | |
|             charWidth: totalWidth / this.length,
 | |
|             textHeight
 | |
|         };
 | |
|     }
 | |
|     coordsAt(pos, side) {
 | |
|         let rect = coordsInChildren(this, pos, side);
 | |
|         // Correct rectangle height for empty lines when the returned
 | |
|         // height is larger than the text height.
 | |
|         if (!this.children.length && rect && this.parent) {
 | |
|             let { heightOracle } = this.parent.view.viewState, height = rect.bottom - rect.top;
 | |
|             if (Math.abs(height - heightOracle.lineHeight) < 2 && heightOracle.textHeight < height) {
 | |
|                 let dist = (height - heightOracle.textHeight) / 2;
 | |
|                 return { top: rect.top + dist, bottom: rect.bottom - dist, left: rect.left, right: rect.left };
 | |
|             }
 | |
|         }
 | |
|         return rect;
 | |
|     }
 | |
|     become(other) {
 | |
|         return other instanceof LineView && this.children.length == 0 && other.children.length == 0 &&
 | |
|             attrsEq(this.attrs, other.attrs) && this.breakAfter == other.breakAfter;
 | |
|     }
 | |
|     covers() { return true; }
 | |
|     static find(docView, pos) {
 | |
|         for (let i = 0, off = 0; i < docView.children.length; i++) {
 | |
|             let block = docView.children[i], end = off + block.length;
 | |
|             if (end >= pos) {
 | |
|                 if (block instanceof LineView)
 | |
|                     return block;
 | |
|                 if (end > pos)
 | |
|                     break;
 | |
|             }
 | |
|             off = end + block.breakAfter;
 | |
|         }
 | |
|         return null;
 | |
|     }
 | |
| }
 | |
| class BlockWidgetView extends ContentView {
 | |
|     constructor(widget, length, deco) {
 | |
|         super();
 | |
|         this.widget = widget;
 | |
|         this.length = length;
 | |
|         this.deco = deco;
 | |
|         this.breakAfter = 0;
 | |
|         this.prevWidget = null;
 | |
|     }
 | |
|     merge(from, to, source, _takeDeco, openStart, openEnd) {
 | |
|         if (source && (!(source instanceof BlockWidgetView) || !this.widget.compare(source.widget) ||
 | |
|             from > 0 && openStart <= 0 || to < this.length && openEnd <= 0))
 | |
|             return false;
 | |
|         this.length = from + (source ? source.length : 0) + (this.length - to);
 | |
|         return true;
 | |
|     }
 | |
|     domAtPos(pos) {
 | |
|         return pos == 0 ? DOMPos.before(this.dom) : DOMPos.after(this.dom, pos == this.length);
 | |
|     }
 | |
|     split(at) {
 | |
|         let len = this.length - at;
 | |
|         this.length = at;
 | |
|         let end = new BlockWidgetView(this.widget, len, this.deco);
 | |
|         end.breakAfter = this.breakAfter;
 | |
|         return end;
 | |
|     }
 | |
|     get children() { return noChildren; }
 | |
|     sync(view) {
 | |
|         if (!this.dom || !this.widget.updateDOM(this.dom, view)) {
 | |
|             if (this.dom && this.prevWidget)
 | |
|                 this.prevWidget.destroy(this.dom);
 | |
|             this.prevWidget = null;
 | |
|             this.setDOM(this.widget.toDOM(view));
 | |
|             if (!this.widget.editable)
 | |
|                 this.dom.contentEditable = "false";
 | |
|         }
 | |
|     }
 | |
|     get overrideDOMText() {
 | |
|         return this.parent ? this.parent.view.state.doc.slice(this.posAtStart, this.posAtEnd) : Text.empty;
 | |
|     }
 | |
|     domBoundsAround() { return null; }
 | |
|     become(other) {
 | |
|         if (other instanceof BlockWidgetView &&
 | |
|             other.widget.constructor == this.widget.constructor) {
 | |
|             if (!other.widget.compare(this.widget))
 | |
|                 this.markDirty(true);
 | |
|             if (this.dom && !this.prevWidget)
 | |
|                 this.prevWidget = this.widget;
 | |
|             this.widget = other.widget;
 | |
|             this.length = other.length;
 | |
|             this.deco = other.deco;
 | |
|             this.breakAfter = other.breakAfter;
 | |
|             return true;
 | |
|         }
 | |
|         return false;
 | |
|     }
 | |
|     ignoreMutation() { return true; }
 | |
|     ignoreEvent(event) { return this.widget.ignoreEvent(event); }
 | |
|     get isEditable() { return false; }
 | |
|     get isWidget() { return true; }
 | |
|     coordsAt(pos, side) {
 | |
|         let custom = this.widget.coordsAt(this.dom, pos, side);
 | |
|         if (custom)
 | |
|             return custom;
 | |
|         if (this.widget instanceof BlockGapWidget)
 | |
|             return null;
 | |
|         return flattenRect(this.dom.getBoundingClientRect(), this.length ? pos == 0 : side <= 0);
 | |
|     }
 | |
|     destroy() {
 | |
|         super.destroy();
 | |
|         if (this.dom)
 | |
|             this.widget.destroy(this.dom);
 | |
|     }
 | |
|     covers(side) {
 | |
|         let { startSide, endSide } = this.deco;
 | |
|         return startSide == endSide ? false : side < 0 ? startSide < 0 : endSide > 0;
 | |
|     }
 | |
| }
 | |
| class BlockGapWidget extends WidgetType {
 | |
|     constructor(height) {
 | |
|         super();
 | |
|         this.height = height;
 | |
|     }
 | |
|     toDOM() {
 | |
|         let elt = document.createElement("div");
 | |
|         elt.className = "cm-gap";
 | |
|         this.updateDOM(elt);
 | |
|         return elt;
 | |
|     }
 | |
|     eq(other) { return other.height == this.height; }
 | |
|     updateDOM(elt) {
 | |
|         elt.style.height = this.height + "px";
 | |
|         return true;
 | |
|     }
 | |
|     get editable() { return true; }
 | |
|     get estimatedHeight() { return this.height; }
 | |
|     ignoreEvent() { return false; }
 | |
| }
 | |
| 
 | |
| class ContentBuilder {
 | |
|     constructor(doc, pos, end, disallowBlockEffectsFor) {
 | |
|         this.doc = doc;
 | |
|         this.pos = pos;
 | |
|         this.end = end;
 | |
|         this.disallowBlockEffectsFor = disallowBlockEffectsFor;
 | |
|         this.content = [];
 | |
|         this.curLine = null;
 | |
|         this.breakAtStart = 0;
 | |
|         this.pendingBuffer = 0 /* Buf.No */;
 | |
|         this.bufferMarks = [];
 | |
|         // Set to false directly after a widget that covers the position after it
 | |
|         this.atCursorPos = true;
 | |
|         this.openStart = -1;
 | |
|         this.openEnd = -1;
 | |
|         this.text = "";
 | |
|         this.textOff = 0;
 | |
|         this.cursor = doc.iter();
 | |
|         this.skip = pos;
 | |
|     }
 | |
|     posCovered() {
 | |
|         if (this.content.length == 0)
 | |
|             return !this.breakAtStart && this.doc.lineAt(this.pos).from != this.pos;
 | |
|         let last = this.content[this.content.length - 1];
 | |
|         return !(last.breakAfter || last instanceof BlockWidgetView && last.deco.endSide < 0);
 | |
|     }
 | |
|     getLine() {
 | |
|         if (!this.curLine) {
 | |
|             this.content.push(this.curLine = new LineView);
 | |
|             this.atCursorPos = true;
 | |
|         }
 | |
|         return this.curLine;
 | |
|     }
 | |
|     flushBuffer(active = this.bufferMarks) {
 | |
|         if (this.pendingBuffer) {
 | |
|             this.curLine.append(wrapMarks(new WidgetBufferView(-1), active), active.length);
 | |
|             this.pendingBuffer = 0 /* Buf.No */;
 | |
|         }
 | |
|     }
 | |
|     addBlockWidget(view) {
 | |
|         this.flushBuffer();
 | |
|         this.curLine = null;
 | |
|         this.content.push(view);
 | |
|     }
 | |
|     finish(openEnd) {
 | |
|         if (this.pendingBuffer && openEnd <= this.bufferMarks.length)
 | |
|             this.flushBuffer();
 | |
|         else
 | |
|             this.pendingBuffer = 0 /* Buf.No */;
 | |
|         if (!this.posCovered() &&
 | |
|             !(openEnd && this.content.length && this.content[this.content.length - 1] instanceof BlockWidgetView))
 | |
|             this.getLine();
 | |
|     }
 | |
|     buildText(length, active, openStart) {
 | |
|         while (length > 0) {
 | |
|             if (this.textOff == this.text.length) {
 | |
|                 let { value, lineBreak, done } = this.cursor.next(this.skip);
 | |
|                 this.skip = 0;
 | |
|                 if (done)
 | |
|                     throw new Error("Ran out of text content when drawing inline views");
 | |
|                 if (lineBreak) {
 | |
|                     if (!this.posCovered())
 | |
|                         this.getLine();
 | |
|                     if (this.content.length)
 | |
|                         this.content[this.content.length - 1].breakAfter = 1;
 | |
|                     else
 | |
|                         this.breakAtStart = 1;
 | |
|                     this.flushBuffer();
 | |
|                     this.curLine = null;
 | |
|                     this.atCursorPos = true;
 | |
|                     length--;
 | |
|                     continue;
 | |
|                 }
 | |
|                 else {
 | |
|                     this.text = value;
 | |
|                     this.textOff = 0;
 | |
|                 }
 | |
|             }
 | |
|             let take = Math.min(this.text.length - this.textOff, length, 512 /* T.Chunk */);
 | |
|             this.flushBuffer(active.slice(active.length - openStart));
 | |
|             this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
 | |
|             this.atCursorPos = true;
 | |
|             this.textOff += take;
 | |
|             length -= take;
 | |
|             openStart = 0;
 | |
|         }
 | |
|     }
 | |
|     span(from, to, active, openStart) {
 | |
|         this.buildText(to - from, active, openStart);
 | |
|         this.pos = to;
 | |
|         if (this.openStart < 0)
 | |
|             this.openStart = openStart;
 | |
|     }
 | |
|     point(from, to, deco, active, openStart, index) {
 | |
|         if (this.disallowBlockEffectsFor[index] && deco instanceof PointDecoration) {
 | |
|             if (deco.block)
 | |
|                 throw new RangeError("Block decorations may not be specified via plugins");
 | |
|             if (to > this.doc.lineAt(this.pos).to)
 | |
|                 throw new RangeError("Decorations that replace line breaks may not be specified via plugins");
 | |
|         }
 | |
|         let len = to - from;
 | |
|         if (deco instanceof PointDecoration) {
 | |
|             if (deco.block) {
 | |
|                 if (deco.startSide > 0 && !this.posCovered())
 | |
|                     this.getLine();
 | |
|                 this.addBlockWidget(new BlockWidgetView(deco.widget || NullWidget.block, len, deco));
 | |
|             }
 | |
|             else {
 | |
|                 let view = WidgetView.create(deco.widget || NullWidget.inline, len, len ? 0 : deco.startSide);
 | |
|                 let cursorBefore = this.atCursorPos && !view.isEditable && openStart <= active.length &&
 | |
|                     (from < to || deco.startSide > 0);
 | |
|                 let cursorAfter = !view.isEditable && (from < to || openStart > active.length || deco.startSide <= 0);
 | |
|                 let line = this.getLine();
 | |
|                 if (this.pendingBuffer == 2 /* Buf.IfCursor */ && !cursorBefore && !view.isEditable)
 | |
|                     this.pendingBuffer = 0 /* Buf.No */;
 | |
|                 this.flushBuffer(active);
 | |
|                 if (cursorBefore) {
 | |
|                     line.append(wrapMarks(new WidgetBufferView(1), active), openStart);
 | |
|                     openStart = active.length + Math.max(0, openStart - active.length);
 | |
|                 }
 | |
|                 line.append(wrapMarks(view, active), openStart);
 | |
|                 this.atCursorPos = cursorAfter;
 | |
|                 this.pendingBuffer = !cursorAfter ? 0 /* Buf.No */ : from < to || openStart > active.length ? 1 /* Buf.Yes */ : 2 /* Buf.IfCursor */;
 | |
|                 if (this.pendingBuffer)
 | |
|                     this.bufferMarks = active.slice();
 | |
|             }
 | |
|         }
 | |
|         else if (this.doc.lineAt(this.pos).from == this.pos) { // Line decoration
 | |
|             this.getLine().addLineDeco(deco);
 | |
|         }
 | |
|         if (len) {
 | |
|             // Advance the iterator past the replaced content
 | |
|             if (this.textOff + len <= this.text.length) {
 | |
|                 this.textOff += len;
 | |
|             }
 | |
|             else {
 | |
|                 this.skip += len - (this.text.length - this.textOff);
 | |
|                 this.text = "";
 | |
|                 this.textOff = 0;
 | |
|             }
 | |
|             this.pos = to;
 | |
|         }
 | |
|         if (this.openStart < 0)
 | |
|             this.openStart = openStart;
 | |
|     }
 | |
|     static build(text, from, to, decorations, dynamicDecorationMap) {
 | |
|         let builder = new ContentBuilder(text, from, to, dynamicDecorationMap);
 | |
|         builder.openEnd = RangeSet.spans(decorations, from, to, builder);
 | |
|         if (builder.openStart < 0)
 | |
|             builder.openStart = builder.openEnd;
 | |
|         builder.finish(builder.openEnd);
 | |
|         return builder;
 | |
|     }
 | |
| }
 | |
| function wrapMarks(view, active) {
 | |
|     for (let mark of active)
 | |
|         view = new MarkView(mark, [view], view.length);
 | |
|     return view;
 | |
| }
 | |
| class NullWidget extends WidgetType {
 | |
|     constructor(tag) {
 | |
|         super();
 | |
|         this.tag = tag;
 | |
|     }
 | |
|     eq(other) { return other.tag == this.tag; }
 | |
|     toDOM() { return document.createElement(this.tag); }
 | |
|     updateDOM(elt) { return elt.nodeName.toLowerCase() == this.tag; }
 | |
|     get isHidden() { return true; }
 | |
| }
 | |
| NullWidget.inline = /*@__PURE__*/new NullWidget("span");
 | |
| NullWidget.block = /*@__PURE__*/new NullWidget("div");
 | |
| 
 | |
| /**
 | |
| Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
 | |
| */
 | |
| var Direction = /*@__PURE__*/(function (Direction) {
 | |
|     // (These are chosen to match the base levels, in bidi algorithm
 | |
|     // terms, of spans in that direction.)
 | |
|     /**
 | |
|     Left-to-right.
 | |
|     */
 | |
|     Direction[Direction["LTR"] = 0] = "LTR";
 | |
|     /**
 | |
|     Right-to-left.
 | |
|     */
 | |
|     Direction[Direction["RTL"] = 1] = "RTL";
 | |
| return Direction})(Direction || (Direction = {}));
 | |
| const LTR = Direction.LTR, RTL = Direction.RTL;
 | |
| // Decode a string with each type encoded as log2(type)
 | |
| function dec(str) {
 | |
|     let result = [];
 | |
|     for (let i = 0; i < str.length; i++)
 | |
|         result.push(1 << +str[i]);
 | |
|     return result;
 | |
| }
 | |
| // Character types for codepoints 0 to 0xf8
 | |
| const LowTypes = /*@__PURE__*/dec("88888888888888888888888888888888888666888888787833333333337888888000000000000000000000000008888880000000000000000000000000088888888888888888888888888888888888887866668888088888663380888308888800000000000000000000000800000000000000000000000000000008");
 | |
| // Character types for codepoints 0x600 to 0x6f9
 | |
| const ArabicTypes = /*@__PURE__*/dec("4444448826627288999999999992222222222222222222222222222222222222222222222229999999999999999999994444444444644222822222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222999999949999999229989999223333333333");
 | |
| const Brackets = /*@__PURE__*/Object.create(null), BracketStack = [];
 | |
| // There's a lot more in
 | |
| // https://www.unicode.org/Public/UCD/latest/ucd/BidiBrackets.txt,
 | |
| // which are left out to keep code size down.
 | |
| for (let p of ["()", "[]", "{}"]) {
 | |
|     let l = /*@__PURE__*/p.charCodeAt(0), r = /*@__PURE__*/p.charCodeAt(1);
 | |
|     Brackets[l] = r;
 | |
|     Brackets[r] = -l;
 | |
| }
 | |
| function charType(ch) {
 | |
|     return ch <= 0xf7 ? LowTypes[ch] :
 | |
|         0x590 <= ch && ch <= 0x5f4 ? 2 /* T.R */ :
 | |
|             0x600 <= ch && ch <= 0x6f9 ? ArabicTypes[ch - 0x600] :
 | |
|                 0x6ee <= ch && ch <= 0x8ac ? 4 /* T.AL */ :
 | |
|                     0x2000 <= ch && ch <= 0x200c ? 256 /* T.NI */ :
 | |
|                         0xfb50 <= ch && ch <= 0xfdff ? 4 /* T.AL */ : 1 /* T.L */;
 | |
| }
 | |
| const BidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac\ufb50-\ufdff]/;
 | |
| /**
 | |
| Represents a contiguous range of text that has a single direction
 | |
| (as in left-to-right or right-to-left).
 | |
| */
 | |
| class BidiSpan {
 | |
|     /**
 | |
|     The direction of this span.
 | |
|     */
 | |
|     get dir() { return this.level % 2 ? RTL : LTR; }
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     constructor(
 | |
|     /**
 | |
|     The start of the span (relative to the start of the line).
 | |
|     */
 | |
|     from, 
 | |
|     /**
 | |
|     The end of the span.
 | |
|     */
 | |
|     to, 
 | |
|     /**
 | |
|     The ["bidi
 | |
|     level"](https://unicode.org/reports/tr9/#Basic_Display_Algorithm)
 | |
|     of the span (in this context, 0 means
 | |
|     left-to-right, 1 means right-to-left, 2 means left-to-right
 | |
|     number inside right-to-left text).
 | |
|     */
 | |
|     level) {
 | |
|         this.from = from;
 | |
|         this.to = to;
 | |
|         this.level = level;
 | |
|     }
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     side(end, dir) { return (this.dir == dir) == end ? this.to : this.from; }
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     forward(forward, dir) { return forward == (this.dir == dir); }
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     static find(order, index, level, assoc) {
 | |
|         let maybe = -1;
 | |
|         for (let i = 0; i < order.length; i++) {
 | |
|             let span = order[i];
 | |
|             if (span.from <= index && span.to >= index) {
 | |
|                 if (span.level == level)
 | |
|                     return i;
 | |
|                 // When multiple spans match, if assoc != 0, take the one that
 | |
|                 // covers that side, otherwise take the one with the minimum
 | |
|                 // level.
 | |
|                 if (maybe < 0 || (assoc != 0 ? (assoc < 0 ? span.from < index : span.to > index) : order[maybe].level > span.level))
 | |
|                     maybe = i;
 | |
|             }
 | |
|         }
 | |
|         if (maybe < 0)
 | |
|             throw new RangeError("Index out of range");
 | |
|         return maybe;
 | |
|     }
 | |
| }
 | |
| function isolatesEq(a, b) {
 | |
|     if (a.length != b.length)
 | |
|         return false;
 | |
|     for (let i = 0; i < a.length; i++) {
 | |
|         let iA = a[i], iB = b[i];
 | |
|         if (iA.from != iB.from || iA.to != iB.to || iA.direction != iB.direction || !isolatesEq(iA.inner, iB.inner))
 | |
|             return false;
 | |
|     }
 | |
|     return true;
 | |
| }
 | |
| // Reused array of character types
 | |
| const types = [];
 | |
| // Fill in the character types (in `types`) from `from` to `to` and
 | |
| // apply W normalization rules.
 | |
| function computeCharTypes(line, rFrom, rTo, isolates, outerType) {
 | |
|     for (let iI = 0; iI <= isolates.length; iI++) {
 | |
|         let from = iI ? isolates[iI - 1].to : rFrom, to = iI < isolates.length ? isolates[iI].from : rTo;
 | |
|         let prevType = iI ? 256 /* T.NI */ : outerType;
 | |
|         // W1. Examine each non-spacing mark (NSM) in the level run, and
 | |
|         // change the type of the NSM to the type of the previous
 | |
|         // character. If the NSM is at the start of the level run, it will
 | |
|         // get the type of sor.
 | |
|         // W2. Search backwards from each instance of a European number
 | |
|         // until the first strong type (R, L, AL, or sor) is found. If an
 | |
|         // AL is found, change the type of the European number to Arabic
 | |
|         // number.
 | |
|         // W3. Change all ALs to R.
 | |
|         // (Left after this: L, R, EN, AN, ET, CS, NI)
 | |
|         for (let i = from, prev = prevType, prevStrong = prevType; i < to; i++) {
 | |
|             let type = charType(line.charCodeAt(i));
 | |
|             if (type == 512 /* T.NSM */)
 | |
|                 type = prev;
 | |
|             else if (type == 8 /* T.EN */ && prevStrong == 4 /* T.AL */)
 | |
|                 type = 16 /* T.AN */;
 | |
|             types[i] = type == 4 /* T.AL */ ? 2 /* T.R */ : type;
 | |
|             if (type & 7 /* T.Strong */)
 | |
|                 prevStrong = type;
 | |
|             prev = type;
 | |
|         }
 | |
|         // W5. A sequence of European terminators adjacent to European
 | |
|         // numbers changes to all European numbers.
 | |
|         // W6. Otherwise, separators and terminators change to Other
 | |
|         // Neutral.
 | |
|         // W7. Search backwards from each instance of a European number
 | |
|         // until the first strong type (R, L, or sor) is found. If an L is
 | |
|         // found, then change the type of the European number to L.
 | |
|         // (Left after this: L, R, EN+AN, NI)
 | |
|         for (let i = from, prev = prevType, prevStrong = prevType; i < to; i++) {
 | |
|             let type = types[i];
 | |
|             if (type == 128 /* T.CS */) {
 | |
|                 if (i < to - 1 && prev == types[i + 1] && (prev & 24 /* T.Num */))
 | |
|                     type = types[i] = prev;
 | |
|                 else
 | |
|                     types[i] = 256 /* T.NI */;
 | |
|             }
 | |
|             else if (type == 64 /* T.ET */) {
 | |
|                 let end = i + 1;
 | |
|                 while (end < to && types[end] == 64 /* T.ET */)
 | |
|                     end++;
 | |
|                 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 */;
 | |
|                 for (let j = i; j < end; j++)
 | |
|                     types[j] = replace;
 | |
|                 i = end - 1;
 | |
|             }
 | |
|             else if (type == 8 /* T.EN */ && prevStrong == 1 /* T.L */) {
 | |
|                 types[i] = 1 /* T.L */;
 | |
|             }
 | |
|             prev = type;
 | |
|             if (type & 7 /* T.Strong */)
 | |
|                 prevStrong = type;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| // Process brackets throughout a run sequence.
 | |
| function processBracketPairs(line, rFrom, rTo, isolates, outerType) {
 | |
|     let oppositeType = outerType == 1 /* T.L */ ? 2 /* T.R */ : 1 /* T.L */;
 | |
|     for (let iI = 0, sI = 0, context = 0; iI <= isolates.length; iI++) {
 | |
|         let from = iI ? isolates[iI - 1].to : rFrom, to = iI < isolates.length ? isolates[iI].from : rTo;
 | |
|         // N0. Process bracket pairs in an isolating run sequence
 | |
|         // sequentially in the logical order of the text positions of the
 | |
|         // opening paired brackets using the logic given below. Within this
 | |
|         // scope, bidirectional types EN and AN are treated as R.
 | |
|         for (let i = from, ch, br, type; i < to; i++) {
 | |
|             // Keeps [startIndex, type, strongSeen] triples for each open
 | |
|             // bracket on BracketStack.
 | |
|             if (br = Brackets[ch = line.charCodeAt(i)]) {
 | |
|                 if (br < 0) { // Closing bracket
 | |
|                     for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
 | |
|                         if (BracketStack[sJ + 1] == -br) {
 | |
|                             let flags = BracketStack[sJ + 2];
 | |
|                             let type = (flags & 2 /* Bracketed.EmbedInside */) ? outerType :
 | |
|                                 !(flags & 4 /* Bracketed.OppositeInside */) ? 0 :
 | |
|                                     (flags & 1 /* Bracketed.OppositeBefore */) ? oppositeType : outerType;
 | |
|                             if (type)
 | |
|                                 types[i] = types[BracketStack[sJ]] = type;
 | |
|                             sI = sJ;
 | |
|                             break;
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|                 else if (BracketStack.length == 189 /* Bracketed.MaxDepth */) {
 | |
|                     break;
 | |
|                 }
 | |
|                 else {
 | |
|                     BracketStack[sI++] = i;
 | |
|                     BracketStack[sI++] = ch;
 | |
|                     BracketStack[sI++] = context;
 | |
|                 }
 | |
|             }
 | |
|             else if ((type = types[i]) == 2 /* T.R */ || type == 1 /* T.L */) {
 | |
|                 let embed = type == outerType;
 | |
|                 context = embed ? 0 : 1 /* Bracketed.OppositeBefore */;
 | |
|                 for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
 | |
|                     let cur = BracketStack[sJ + 2];
 | |
|                     if (cur & 2 /* Bracketed.EmbedInside */)
 | |
|                         break;
 | |
|                     if (embed) {
 | |
|                         BracketStack[sJ + 2] |= 2 /* Bracketed.EmbedInside */;
 | |
|                     }
 | |
|                     else {
 | |
|                         if (cur & 4 /* Bracketed.OppositeInside */)
 | |
|                             break;
 | |
|                         BracketStack[sJ + 2] |= 4 /* Bracketed.OppositeInside */;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function processNeutrals(rFrom, rTo, isolates, outerType) {
 | |
|     for (let iI = 0, prev = outerType; iI <= isolates.length; iI++) {
 | |
|         let from = iI ? isolates[iI - 1].to : rFrom, to = iI < isolates.length ? isolates[iI].from : rTo;
 | |
|         // N1. A sequence of neutrals takes the direction of the
 | |
|         // surrounding strong text if the text on both sides has the same
 | |
|         // direction. European and Arabic numbers act as if they were R in
 | |
|         // terms of their influence on neutrals. Start-of-level-run (sor)
 | |
|         // and end-of-level-run (eor) are used at level run boundaries.
 | |
|         // N2. Any remaining neutrals take the embedding direction.
 | |
|         // (Left after this: L, R, EN+AN)
 | |
|         for (let i = from; i < to;) {
 | |
|             let type = types[i];
 | |
|             if (type == 256 /* T.NI */) {
 | |
|                 let end = i + 1;
 | |
|                 for (;;) {
 | |
|                     if (end == to) {
 | |
|                         if (iI == isolates.length)
 | |
|                             break;
 | |
|                         end = isolates[iI++].to;
 | |
|                         to = iI < isolates.length ? isolates[iI].from : rTo;
 | |
|                     }
 | |
|                     else if (types[end] == 256 /* T.NI */) {
 | |
|                         end++;
 | |
|                     }
 | |
|                     else {
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
|                 let beforeL = prev == 1 /* T.L */;
 | |
|                 let afterL = (end < rTo ? types[end] : outerType) == 1 /* T.L */;
 | |
|                 let replace = beforeL == afterL ? (beforeL ? 1 /* T.L */ : 2 /* T.R */) : outerType;
 | |
|                 for (let j = end, jI = iI, fromJ = jI ? isolates[jI - 1].to : rFrom; j > i;) {
 | |
|                     if (j == fromJ) {
 | |
|                         j = isolates[--jI].from;
 | |
|                         fromJ = jI ? isolates[jI - 1].to : rFrom;
 | |
|                     }
 | |
|                     types[--j] = replace;
 | |
|                 }
 | |
|                 i = end;
 | |
|             }
 | |
|             else {
 | |
|                 prev = type;
 | |
|                 i++;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| // Find the contiguous ranges of character types in a given range, and
 | |
| // emit spans for them. Flip the order of the spans as appropriate
 | |
| // based on the level, and call through to compute the spans for
 | |
| // isolates at the proper point.
 | |
| function emitSpans(line, from, to, level, baseLevel, isolates, order) {
 | |
|     let ourType = level % 2 ? 2 /* T.R */ : 1 /* T.L */;
 | |
|     if ((level % 2) == (baseLevel % 2)) { // Same dir as base direction, don't flip
 | |
|         for (let iCh = from, iI = 0; iCh < to;) {
 | |
|             // Scan a section of characters in direction ourType, unless
 | |
|             // there's another type of char right after iCh, in which case
 | |
|             // we scan a section of other characters (which, if ourType ==
 | |
|             // T.L, may contain both T.R and T.AN chars).
 | |
|             let sameDir = true, isNum = false;
 | |
|             if (iI == isolates.length || iCh < isolates[iI].from) {
 | |
|                 let next = types[iCh];
 | |
|                 if (next != ourType) {
 | |
|                     sameDir = false;
 | |
|                     isNum = next == 16 /* T.AN */;
 | |
|                 }
 | |
|             }
 | |
|             // Holds an array of isolates to pass to a recursive call if we
 | |
|             // must recurse (to distinguish T.AN inside an RTL section in
 | |
|             // LTR text), null if we can emit directly
 | |
|             let recurse = !sameDir && ourType == 1 /* T.L */ ? [] : null;
 | |
|             let localLevel = sameDir ? level : level + 1;
 | |
|             let iScan = iCh;
 | |
|             run: for (;;) {
 | |
|                 if (iI < isolates.length && iScan == isolates[iI].from) {
 | |
|                     if (isNum)
 | |
|                         break run;
 | |
|                     let iso = isolates[iI];
 | |
|                     // Scan ahead to verify that there is another char in this dir after the isolate(s)
 | |
|                     if (!sameDir)
 | |
|                         for (let upto = iso.to, jI = iI + 1;;) {
 | |
|                             if (upto == to)
 | |
|                                 break run;
 | |
|                             if (jI < isolates.length && isolates[jI].from == upto)
 | |
|                                 upto = isolates[jI++].to;
 | |
|                             else if (types[upto] == ourType)
 | |
|                                 break run;
 | |
|                             else
 | |
|                                 break;
 | |
|                         }
 | |
|                     iI++;
 | |
|                     if (recurse) {
 | |
|                         recurse.push(iso);
 | |
|                     }
 | |
|                     else {
 | |
|                         if (iso.from > iCh)
 | |
|                             order.push(new BidiSpan(iCh, iso.from, localLevel));
 | |
|                         let dirSwap = (iso.direction == LTR) != !(localLevel % 2);
 | |
|                         computeSectionOrder(line, dirSwap ? level + 1 : level, baseLevel, iso.inner, iso.from, iso.to, order);
 | |
|                         iCh = iso.to;
 | |
|                     }
 | |
|                     iScan = iso.to;
 | |
|                 }
 | |
|                 else if (iScan == to || (sameDir ? types[iScan] != ourType : types[iScan] == ourType)) {
 | |
|                     break;
 | |
|                 }
 | |
|                 else {
 | |
|                     iScan++;
 | |
|                 }
 | |
|             }
 | |
|             if (recurse)
 | |
|                 emitSpans(line, iCh, iScan, level + 1, baseLevel, recurse, order);
 | |
|             else if (iCh < iScan)
 | |
|                 order.push(new BidiSpan(iCh, iScan, localLevel));
 | |
|             iCh = iScan;
 | |
|         }
 | |
|     }
 | |
|     else {
 | |
|         // Iterate in reverse to flip the span order. Same code again, but
 | |
|         // going from the back of the section to the front
 | |
|         for (let iCh = to, iI = isolates.length; iCh > from;) {
 | |
|             let sameDir = true, isNum = false;
 | |
|             if (!iI || iCh > isolates[iI - 1].to) {
 | |
|                 let next = types[iCh - 1];
 | |
|                 if (next != ourType) {
 | |
|                     sameDir = false;
 | |
|                     isNum = next == 16 /* T.AN */;
 | |
|                 }
 | |
|             }
 | |
|             let recurse = !sameDir && ourType == 1 /* T.L */ ? [] : null;
 | |
|             let localLevel = sameDir ? level : level + 1;
 | |
|             let iScan = iCh;
 | |
|             run: for (;;) {
 | |
|                 if (iI && iScan == isolates[iI - 1].to) {
 | |
|                     if (isNum)
 | |
|                         break run;
 | |
|                     let iso = isolates[--iI];
 | |
|                     // Scan ahead to verify that there is another char in this dir after the isolate(s)
 | |
|                     if (!sameDir)
 | |
|                         for (let upto = iso.from, jI = iI;;) {
 | |
|                             if (upto == from)
 | |
|                                 break run;
 | |
|                             if (jI && isolates[jI - 1].to == upto)
 | |
|                                 upto = isolates[--jI].from;
 | |
|                             else if (types[upto - 1] == ourType)
 | |
|                                 break run;
 | |
|                             else
 | |
|                                 break;
 | |
|                         }
 | |
|                     if (recurse) {
 | |
|                         recurse.push(iso);
 | |
|                     }
 | |
|                     else {
 | |
|                         if (iso.to < iCh)
 | |
|                             order.push(new BidiSpan(iso.to, iCh, localLevel));
 | |
|                         let dirSwap = (iso.direction == LTR) != !(localLevel % 2);
 | |
|                         computeSectionOrder(line, dirSwap ? level + 1 : level, baseLevel, iso.inner, iso.from, iso.to, order);
 | |
|                         iCh = iso.from;
 | |
|                     }
 | |
|                     iScan = iso.from;
 | |
|                 }
 | |
|                 else if (iScan == from || (sameDir ? types[iScan - 1] != ourType : types[iScan - 1] == ourType)) {
 | |
|                     break;
 | |
|                 }
 | |
|                 else {
 | |
|                     iScan--;
 | |
|                 }
 | |
|             }
 | |
|             if (recurse)
 | |
|                 emitSpans(line, iScan, iCh, level + 1, baseLevel, recurse, order);
 | |
|             else if (iScan < iCh)
 | |
|                 order.push(new BidiSpan(iScan, iCh, localLevel));
 | |
|             iCh = iScan;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function computeSectionOrder(line, level, baseLevel, isolates, from, to, order) {
 | |
|     let outerType = (level % 2 ? 2 /* T.R */ : 1 /* T.L */);
 | |
|     computeCharTypes(line, from, to, isolates, outerType);
 | |
|     processBracketPairs(line, from, to, isolates, outerType);
 | |
|     processNeutrals(from, to, isolates, outerType);
 | |
|     emitSpans(line, from, to, level, baseLevel, isolates, order);
 | |
| }
 | |
| function computeOrder(line, direction, isolates) {
 | |
|     if (!line)
 | |
|         return [new BidiSpan(0, 0, direction == RTL ? 1 : 0)];
 | |
|     if (direction == LTR && !isolates.length && !BidiRE.test(line))
 | |
|         return trivialOrder(line.length);
 | |
|     if (isolates.length)
 | |
|         while (line.length > types.length)
 | |
|             types[types.length] = 256 /* T.NI */; // Make sure types array has no gaps
 | |
|     let order = [], level = direction == LTR ? 0 : 1;
 | |
|     computeSectionOrder(line, level, level, isolates, 0, line.length, order);
 | |
|     return order;
 | |
| }
 | |
| function trivialOrder(length) {
 | |
|     return [new BidiSpan(0, length, 0)];
 | |
| }
 | |
| let movedOver = "";
 | |
| // This implementation moves strictly visually, without concern for a
 | |
| // traversal visiting every logical position in the string. It will
 | |
| // still do so for simple input, but situations like multiple isolates
 | |
| // with the same level next to each other, or text going against the
 | |
| // main dir at the end of the line, will make some positions
 | |
| // unreachable with this motion. Each visible cursor position will
 | |
| // correspond to the lower-level bidi span that touches it.
 | |
| //
 | |
| // The alternative would be to solve an order globally for a given
 | |
| // line, making sure that it includes every position, but that would
 | |
| // require associating non-canonical (higher bidi span level)
 | |
| // positions with a given visual position, which is likely to confuse
 | |
| // people. (And would generally be a lot more complicated.)
 | |
| function moveVisually(line, order, dir, start, forward) {
 | |
|     var _a;
 | |
|     let startIndex = start.head - line.from;
 | |
|     let spanI = BidiSpan.find(order, startIndex, (_a = start.bidiLevel) !== null && _a !== void 0 ? _a : -1, start.assoc);
 | |
|     let span = order[spanI], spanEnd = span.side(forward, dir);
 | |
|     // End of span
 | |
|     if (startIndex == spanEnd) {
 | |
|         let nextI = spanI += forward ? 1 : -1;
 | |
|         if (nextI < 0 || nextI >= order.length)
 | |
|             return null;
 | |
|         span = order[spanI = nextI];
 | |
|         startIndex = span.side(!forward, dir);
 | |
|         spanEnd = span.side(forward, dir);
 | |
|     }
 | |
|     let nextIndex = findClusterBreak(line.text, startIndex, span.forward(forward, dir));
 | |
|     if (nextIndex < span.from || nextIndex > span.to)
 | |
|         nextIndex = spanEnd;
 | |
|     movedOver = line.text.slice(Math.min(startIndex, nextIndex), Math.max(startIndex, nextIndex));
 | |
|     let nextSpan = spanI == (forward ? order.length - 1 : 0) ? null : order[spanI + (forward ? 1 : -1)];
 | |
|     if (nextSpan && nextIndex == spanEnd && nextSpan.level + (forward ? 0 : 1) < span.level)
 | |
|         return EditorSelection.cursor(nextSpan.side(!forward, dir) + line.from, nextSpan.forward(forward, dir) ? 1 : -1, nextSpan.level);
 | |
|     return EditorSelection.cursor(nextIndex + line.from, span.forward(forward, dir) ? -1 : 1, span.level);
 | |
| }
 | |
| function autoDirection(text, from, to) {
 | |
|     for (let i = from; i < to; i++) {
 | |
|         let type = charType(text.charCodeAt(i));
 | |
|         if (type == 1 /* T.L */)
 | |
|             return LTR;
 | |
|         if (type == 2 /* T.R */ || type == 4 /* T.AL */)
 | |
|             return RTL;
 | |
|     }
 | |
|     return LTR;
 | |
| }
 | |
| 
 | |
| const clickAddsSelectionRange = /*@__PURE__*/Facet.define();
 | |
| const dragMovesSelection$1 = /*@__PURE__*/Facet.define();
 | |
| const mouseSelectionStyle = /*@__PURE__*/Facet.define();
 | |
| const exceptionSink = /*@__PURE__*/Facet.define();
 | |
| const updateListener = /*@__PURE__*/Facet.define();
 | |
| const inputHandler = /*@__PURE__*/Facet.define();
 | |
| const focusChangeEffect = /*@__PURE__*/Facet.define();
 | |
| const perLineTextDirection = /*@__PURE__*/Facet.define({
 | |
|     combine: values => values.some(x => x)
 | |
| });
 | |
| const nativeSelectionHidden = /*@__PURE__*/Facet.define({
 | |
|     combine: values => values.some(x => x)
 | |
| });
 | |
| const scrollHandler = /*@__PURE__*/Facet.define();
 | |
| class ScrollTarget {
 | |
|     constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5, 
 | |
|     // This data structure is abused to also store precise scroll
 | |
|     // snapshots, instead of a `scrollIntoView` request. When this
 | |
|     // flag is `true`, `range` points at a position in the reference
 | |
|     // line, `yMargin` holds the difference between the top of that
 | |
|     // line and the top of the editor, and `xMargin` holds the
 | |
|     // editor's `scrollLeft`.
 | |
|     isSnapshot = false) {
 | |
|         this.range = range;
 | |
|         this.y = y;
 | |
|         this.x = x;
 | |
|         this.yMargin = yMargin;
 | |
|         this.xMargin = xMargin;
 | |
|         this.isSnapshot = isSnapshot;
 | |
|     }
 | |
|     map(changes) {
 | |
|         return changes.empty ? this :
 | |
|             new ScrollTarget(this.range.map(changes), this.y, this.x, this.yMargin, this.xMargin, this.isSnapshot);
 | |
|     }
 | |
|     clip(state) {
 | |
|         return this.range.to <= state.doc.length ? this :
 | |
|             new ScrollTarget(EditorSelection.cursor(state.doc.length), this.y, this.x, this.yMargin, this.xMargin, this.isSnapshot);
 | |
|     }
 | |
| }
 | |
| const scrollIntoView = /*@__PURE__*/StateEffect.define({ map: (t, ch) => t.map(ch) });
 | |
| const setEditContextFormatting = /*@__PURE__*/StateEffect.define();
 | |
| /**
 | |
| Log or report an unhandled exception in client code. Should
 | |
| probably only be used by extension code that allows client code to
 | |
| provide functions, and calls those functions in a context where an
 | |
| exception can't be propagated to calling code in a reasonable way
 | |
| (for example when in an event handler).
 | |
| 
 | |
| Either calls a handler registered with
 | |
| [`EditorView.exceptionSink`](https://codemirror.net/6/docs/ref/#view.EditorView^exceptionSink),
 | |
| `window.onerror`, if defined, or `console.error` (in which case
 | |
| it'll pass `context`, when given, as first argument).
 | |
| */
 | |
| function logException(state, exception, context) {
 | |
|     let handler = state.facet(exceptionSink);
 | |
|     if (handler.length)
 | |
|         handler[0](exception);
 | |
|     else if (window.onerror)
 | |
|         window.onerror(String(exception), context, undefined, undefined, exception);
 | |
|     else if (context)
 | |
|         console.error(context + ":", exception);
 | |
|     else
 | |
|         console.error(exception);
 | |
| }
 | |
| const editable = /*@__PURE__*/Facet.define({ combine: values => values.length ? values[0] : true });
 | |
| let nextPluginID = 0;
 | |
| const viewPlugin = /*@__PURE__*/Facet.define();
 | |
| /**
 | |
| View plugins associate stateful values with a view. They can
 | |
| influence the way the content is drawn, and are notified of things
 | |
| that happen in the view.
 | |
| */
 | |
| class ViewPlugin {
 | |
|     constructor(
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     id, 
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     create, 
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     domEventHandlers, 
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     domEventObservers, buildExtensions) {
 | |
|         this.id = id;
 | |
|         this.create = create;
 | |
|         this.domEventHandlers = domEventHandlers;
 | |
|         this.domEventObservers = domEventObservers;
 | |
|         this.extension = buildExtensions(this);
 | |
|     }
 | |
|     /**
 | |
|     Define a plugin from a constructor function that creates the
 | |
|     plugin's value, given an editor view.
 | |
|     */
 | |
|     static define(create, spec) {
 | |
|         const { eventHandlers, eventObservers, provide, decorations: deco } = spec || {};
 | |
|         return new ViewPlugin(nextPluginID++, create, eventHandlers, eventObservers, plugin => {
 | |
|             let ext = [viewPlugin.of(plugin)];
 | |
|             if (deco)
 | |
|                 ext.push(decorations.of(view => {
 | |
|                     let pluginInst = view.plugin(plugin);
 | |
|                     return pluginInst ? deco(pluginInst) : Decoration.none;
 | |
|                 }));
 | |
|             if (provide)
 | |
|                 ext.push(provide(plugin));
 | |
|             return ext;
 | |
|         });
 | |
|     }
 | |
|     /**
 | |
|     Create a plugin for a class whose constructor takes a single
 | |
|     editor view as argument.
 | |
|     */
 | |
|     static fromClass(cls, spec) {
 | |
|         return ViewPlugin.define(view => new cls(view), spec);
 | |
|     }
 | |
| }
 | |
| class PluginInstance {
 | |
|     constructor(spec) {
 | |
|         this.spec = spec;
 | |
|         // When starting an update, all plugins have this field set to the
 | |
|         // update object, indicating they need to be updated. When finished
 | |
|         // updating, it is set to `false`. Retrieving a plugin that needs to
 | |
|         // be updated with `view.plugin` forces an eager update.
 | |
|         this.mustUpdate = null;
 | |
|         // This is null when the plugin is initially created, but
 | |
|         // initialized on the first update.
 | |
|         this.value = null;
 | |
|     }
 | |
|     update(view) {
 | |
|         if (!this.value) {
 | |
|             if (this.spec) {
 | |
|                 try {
 | |
|                     this.value = this.spec.create(view);
 | |
|                 }
 | |
|                 catch (e) {
 | |
|                     logException(view.state, e, "CodeMirror plugin crashed");
 | |
|                     this.deactivate();
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         else if (this.mustUpdate) {
 | |
|             let update = this.mustUpdate;
 | |
|             this.mustUpdate = null;
 | |
|             if (this.value.update) {
 | |
|                 try {
 | |
|                     this.value.update(update);
 | |
|                 }
 | |
|                 catch (e) {
 | |
|                     logException(update.state, e, "CodeMirror plugin crashed");
 | |
|                     if (this.value.destroy)
 | |
|                         try {
 | |
|                             this.value.destroy();
 | |
|                         }
 | |
|                         catch (_) { }
 | |
|                     this.deactivate();
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         return this;
 | |
|     }
 | |
|     destroy(view) {
 | |
|         var _a;
 | |
|         if ((_a = this.value) === null || _a === void 0 ? void 0 : _a.destroy) {
 | |
|             try {
 | |
|                 this.value.destroy();
 | |
|             }
 | |
|             catch (e) {
 | |
|                 logException(view.state, e, "CodeMirror plugin crashed");
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     deactivate() {
 | |
|         this.spec = this.value = null;
 | |
|     }
 | |
| }
 | |
| const editorAttributes = /*@__PURE__*/Facet.define();
 | |
| const contentAttributes = /*@__PURE__*/Facet.define();
 | |
| // Provide decorations
 | |
| const decorations = /*@__PURE__*/Facet.define();
 | |
| const outerDecorations = /*@__PURE__*/Facet.define();
 | |
| const atomicRanges = /*@__PURE__*/Facet.define();
 | |
| const bidiIsolatedRanges = /*@__PURE__*/Facet.define();
 | |
| function getIsolatedRanges(view, line) {
 | |
|     let isolates = view.state.facet(bidiIsolatedRanges);
 | |
|     if (!isolates.length)
 | |
|         return isolates;
 | |
|     let sets = isolates.map(i => i instanceof Function ? i(view) : i);
 | |
|     let result = [];
 | |
|     RangeSet.spans(sets, line.from, line.to, {
 | |
|         point() { },
 | |
|         span(fromDoc, toDoc, active, open) {
 | |
|             let from = fromDoc - line.from, to = toDoc - line.from;
 | |
|             let level = result;
 | |
|             for (let i = active.length - 1; i >= 0; i--, open--) {
 | |
|                 let direction = active[i].spec.bidiIsolate, update;
 | |
|                 if (direction == null)
 | |
|                     direction = autoDirection(line.text, from, to);
 | |
|                 if (open > 0 && level.length &&
 | |
|                     (update = level[level.length - 1]).to == from && update.direction == direction) {
 | |
|                     update.to = to;
 | |
|                     level = update.inner;
 | |
|                 }
 | |
|                 else {
 | |
|                     let add = { from, to, direction, inner: [] };
 | |
|                     level.push(add);
 | |
|                     level = add.inner;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     });
 | |
|     return result;
 | |
| }
 | |
| const scrollMargins = /*@__PURE__*/Facet.define();
 | |
| function getScrollMargins(view) {
 | |
|     let left = 0, right = 0, top = 0, bottom = 0;
 | |
|     for (let source of view.state.facet(scrollMargins)) {
 | |
|         let m = source(view);
 | |
|         if (m) {
 | |
|             if (m.left != null)
 | |
|                 left = Math.max(left, m.left);
 | |
|             if (m.right != null)
 | |
|                 right = Math.max(right, m.right);
 | |
|             if (m.top != null)
 | |
|                 top = Math.max(top, m.top);
 | |
|             if (m.bottom != null)
 | |
|                 bottom = Math.max(bottom, m.bottom);
 | |
|         }
 | |
|     }
 | |
|     return { left, right, top, bottom };
 | |
| }
 | |
| const styleModule = /*@__PURE__*/Facet.define();
 | |
| class ChangedRange {
 | |
|     constructor(fromA, toA, fromB, toB) {
 | |
|         this.fromA = fromA;
 | |
|         this.toA = toA;
 | |
|         this.fromB = fromB;
 | |
|         this.toB = toB;
 | |
|     }
 | |
|     join(other) {
 | |
|         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));
 | |
|     }
 | |
|     addToSet(set) {
 | |
|         let i = set.length, me = this;
 | |
|         for (; i > 0; i--) {
 | |
|             let range = set[i - 1];
 | |
|             if (range.fromA > me.toA)
 | |
|                 continue;
 | |
|             if (range.toA < me.fromA)
 | |
|                 break;
 | |
|             me = me.join(range);
 | |
|             set.splice(i - 1, 1);
 | |
|         }
 | |
|         set.splice(i, 0, me);
 | |
|         return set;
 | |
|     }
 | |
|     static extendWithRanges(diff, ranges) {
 | |
|         if (ranges.length == 0)
 | |
|             return diff;
 | |
|         let result = [];
 | |
|         for (let dI = 0, rI = 0, posA = 0, posB = 0;; dI++) {
 | |
|             let next = dI == diff.length ? null : diff[dI], off = posA - posB;
 | |
|             let end = next ? next.fromB : 1e9;
 | |
|             while (rI < ranges.length && ranges[rI] < end) {
 | |
|                 let from = ranges[rI], to = ranges[rI + 1];
 | |
|                 let fromB = Math.max(posB, from), toB = Math.min(end, to);
 | |
|                 if (fromB <= toB)
 | |
|                     new ChangedRange(fromB + off, toB + off, fromB, toB).addToSet(result);
 | |
|                 if (to > end)
 | |
|                     break;
 | |
|                 else
 | |
|                     rI += 2;
 | |
|             }
 | |
|             if (!next)
 | |
|                 return result;
 | |
|             new ChangedRange(next.fromA, next.toA, next.fromB, next.toB).addToSet(result);
 | |
|             posA = next.toA;
 | |
|             posB = next.toB;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| /**
 | |
| View [plugins](https://codemirror.net/6/docs/ref/#view.ViewPlugin) are given instances of this
 | |
| class, which describe what happened, whenever the view is updated.
 | |
| */
 | |
| class ViewUpdate {
 | |
|     constructor(
 | |
|     /**
 | |
|     The editor view that the update is associated with.
 | |
|     */
 | |
|     view, 
 | |
|     /**
 | |
|     The new editor state.
 | |
|     */
 | |
|     state, 
 | |
|     /**
 | |
|     The transactions involved in the update. May be empty.
 | |
|     */
 | |
|     transactions) {
 | |
|         this.view = view;
 | |
|         this.state = state;
 | |
|         this.transactions = transactions;
 | |
|         /**
 | |
|         @internal
 | |
|         */
 | |
|         this.flags = 0;
 | |
|         this.startState = view.state;
 | |
|         this.changes = ChangeSet.empty(this.startState.doc.length);
 | |
|         for (let tr of transactions)
 | |
|             this.changes = this.changes.compose(tr.changes);
 | |
|         let changedRanges = [];
 | |
|         this.changes.iterChangedRanges((fromA, toA, fromB, toB) => changedRanges.push(new ChangedRange(fromA, toA, fromB, toB)));
 | |
|         this.changedRanges = changedRanges;
 | |
|     }
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     static create(view, state, transactions) {
 | |
|         return new ViewUpdate(view, state, transactions);
 | |
|     }
 | |
|     /**
 | |
|     Tells you whether the [viewport](https://codemirror.net/6/docs/ref/#view.EditorView.viewport) or
 | |
|     [visible ranges](https://codemirror.net/6/docs/ref/#view.EditorView.visibleRanges) changed in this
 | |
|     update.
 | |
|     */
 | |
|     get viewportChanged() {
 | |
|         return (this.flags & 4 /* UpdateFlag.Viewport */) > 0;
 | |
|     }
 | |
|     /**
 | |
|     Indicates whether the height of a block element in the editor
 | |
|     changed in this update.
 | |
|     */
 | |
|     get heightChanged() {
 | |
|         return (this.flags & 2 /* UpdateFlag.Height */) > 0;
 | |
|     }
 | |
|     /**
 | |
|     Returns true when the document was modified or the size of the
 | |
|     editor, or elements within the editor, changed.
 | |
|     */
 | |
|     get geometryChanged() {
 | |
|         return this.docChanged || (this.flags & (8 /* UpdateFlag.Geometry */ | 2 /* UpdateFlag.Height */)) > 0;
 | |
|     }
 | |
|     /**
 | |
|     True when this update indicates a focus change.
 | |
|     */
 | |
|     get focusChanged() {
 | |
|         return (this.flags & 1 /* UpdateFlag.Focus */) > 0;
 | |
|     }
 | |
|     /**
 | |
|     Whether the document changed in this update.
 | |
|     */
 | |
|     get docChanged() {
 | |
|         return !this.changes.empty;
 | |
|     }
 | |
|     /**
 | |
|     Whether the selection was explicitly set in this update.
 | |
|     */
 | |
|     get selectionSet() {
 | |
|         return this.transactions.some(tr => tr.selection);
 | |
|     }
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     get empty() { return this.flags == 0 && this.transactions.length == 0; }
 | |
| }
 | |
| 
 | |
| class DocView extends ContentView {
 | |
|     get length() { return this.view.state.doc.length; }
 | |
|     constructor(view) {
 | |
|         super();
 | |
|         this.view = view;
 | |
|         this.decorations = [];
 | |
|         this.dynamicDecorationMap = [false];
 | |
|         this.domChanged = null;
 | |
|         this.hasComposition = null;
 | |
|         this.markedForComposition = new Set;
 | |
|         this.editContextFormatting = Decoration.none;
 | |
|         this.lastCompositionAfterCursor = false;
 | |
|         // Track a minimum width for the editor. When measuring sizes in
 | |
|         // measureVisibleLineHeights, this is updated to point at the width
 | |
|         // of a given element and its extent in the document. When a change
 | |
|         // happens in that range, these are reset. That way, once we've seen
 | |
|         // a line/element of a given length, we keep the editor wide enough
 | |
|         // to fit at least that element, until it is changed, at which point
 | |
|         // we forget it again.
 | |
|         this.minWidth = 0;
 | |
|         this.minWidthFrom = 0;
 | |
|         this.minWidthTo = 0;
 | |
|         // Track whether the DOM selection was set in a lossy way, so that
 | |
|         // we don't mess it up when reading it back it
 | |
|         this.impreciseAnchor = null;
 | |
|         this.impreciseHead = null;
 | |
|         this.forceSelection = false;
 | |
|         // Used by the resize observer to ignore resizes that we caused
 | |
|         // ourselves
 | |
|         this.lastUpdate = Date.now();
 | |
|         this.setDOM(view.contentDOM);
 | |
|         this.children = [new LineView];
 | |
|         this.children[0].setParent(this);
 | |
|         this.updateDeco();
 | |
|         this.updateInner([new ChangedRange(0, 0, 0, view.state.doc.length)], 0, null);
 | |
|     }
 | |
|     // Update the document view to a given state.
 | |
|     update(update) {
 | |
|         var _a;
 | |
|         let changedRanges = update.changedRanges;
 | |
|         if (this.minWidth > 0 && changedRanges.length) {
 | |
|             if (!changedRanges.every(({ fromA, toA }) => toA < this.minWidthFrom || fromA > this.minWidthTo)) {
 | |
|                 this.minWidth = this.minWidthFrom = this.minWidthTo = 0;
 | |
|             }
 | |
|             else {
 | |
|                 this.minWidthFrom = update.changes.mapPos(this.minWidthFrom, 1);
 | |
|                 this.minWidthTo = update.changes.mapPos(this.minWidthTo, 1);
 | |
|             }
 | |
|         }
 | |
|         this.updateEditContextFormatting(update);
 | |
|         let readCompositionAt = -1;
 | |
|         if (this.view.inputState.composing >= 0 && !this.view.observer.editContext) {
 | |
|             if ((_a = this.domChanged) === null || _a === void 0 ? void 0 : _a.newSel)
 | |
|                 readCompositionAt = this.domChanged.newSel.head;
 | |
|             else if (!touchesComposition(update.changes, this.hasComposition) && !update.selectionSet)
 | |
|                 readCompositionAt = update.state.selection.main.head;
 | |
|         }
 | |
|         let composition = readCompositionAt > -1 ? findCompositionRange(this.view, update.changes, readCompositionAt) : null;
 | |
|         this.domChanged = null;
 | |
|         if (this.hasComposition) {
 | |
|             this.markedForComposition.clear();
 | |
|             let { from, to } = this.hasComposition;
 | |
|             changedRanges = new ChangedRange(from, to, update.changes.mapPos(from, -1), update.changes.mapPos(to, 1))
 | |
|                 .addToSet(changedRanges.slice());
 | |
|         }
 | |
|         this.hasComposition = composition ? { from: composition.range.fromB, to: composition.range.toB } : null;
 | |
|         // When the DOM nodes around the selection are moved to another
 | |
|         // parent, Chrome sometimes reports a different selection through
 | |
|         // getSelection than the one that it actually shows to the user.
 | |
|         // This forces a selection update when lines are joined to work
 | |
|         // around that. Issue #54
 | |
|         if ((browser.ie || browser.chrome) && !composition && update &&
 | |
|             update.state.doc.lines != update.startState.doc.lines)
 | |
|             this.forceSelection = true;
 | |
|         let prevDeco = this.decorations, deco = this.updateDeco();
 | |
|         let decoDiff = findChangedDeco(prevDeco, deco, update.changes);
 | |
|         changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
 | |
|         if (!(this.flags & 7 /* ViewFlag.Dirty */) && changedRanges.length == 0) {
 | |
|             return false;
 | |
|         }
 | |
|         else {
 | |
|             this.updateInner(changedRanges, update.startState.doc.length, composition);
 | |
|             if (update.transactions.length)
 | |
|                 this.lastUpdate = Date.now();
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
|     // Used by update and the constructor do perform the actual DOM
 | |
|     // update
 | |
|     updateInner(changes, oldLength, composition) {
 | |
|         this.view.viewState.mustMeasureContent = true;
 | |
|         this.updateChildren(changes, oldLength, composition);
 | |
|         let { observer } = this.view;
 | |
|         observer.ignore(() => {
 | |
|             // Lock the height during redrawing, since Chrome sometimes
 | |
|             // messes with the scroll position during DOM mutation (though
 | |
|             // no relayout is triggered and I cannot imagine how it can
 | |
|             // recompute the scroll position without a layout)
 | |
|             this.dom.style.height = this.view.viewState.contentHeight / this.view.scaleY + "px";
 | |
|             this.dom.style.flexBasis = this.minWidth ? this.minWidth + "px" : "";
 | |
|             // Chrome will sometimes, when DOM mutations occur directly
 | |
|             // around the selection, get confused and report a different
 | |
|             // selection from the one it displays (issue #218). This tries
 | |
|             // to detect that situation.
 | |
|             let track = browser.chrome || browser.ios ? { node: observer.selectionRange.focusNode, written: false } : undefined;
 | |
|             this.sync(this.view, track);
 | |
|             this.flags &= ~7 /* ViewFlag.Dirty */;
 | |
|             if (track && (track.written || observer.selectionRange.focusNode != track.node))
 | |
|                 this.forceSelection = true;
 | |
|             this.dom.style.height = "";
 | |
|         });
 | |
|         this.markedForComposition.forEach(cView => cView.flags &= ~8 /* ViewFlag.Composition */);
 | |
|         let gaps = [];
 | |
|         if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
 | |
|             for (let child of this.children)
 | |
|                 if (child instanceof BlockWidgetView && child.widget instanceof BlockGapWidget)
 | |
|                     gaps.push(child.dom);
 | |
|         observer.updateGaps(gaps);
 | |
|     }
 | |
|     updateChildren(changes, oldLength, composition) {
 | |
|         let ranges = composition ? composition.range.addToSet(changes.slice()) : changes;
 | |
|         let cursor = this.childCursor(oldLength);
 | |
|         for (let i = ranges.length - 1;; i--) {
 | |
|             let next = i >= 0 ? ranges[i] : null;
 | |
|             if (!next)
 | |
|                 break;
 | |
|             let { fromA, toA, fromB, toB } = next, content, breakAtStart, openStart, openEnd;
 | |
|             if (composition && composition.range.fromB < toB && composition.range.toB > fromB) {
 | |
|                 let before = ContentBuilder.build(this.view.state.doc, fromB, composition.range.fromB, this.decorations, this.dynamicDecorationMap);
 | |
|                 let after = ContentBuilder.build(this.view.state.doc, composition.range.toB, toB, this.decorations, this.dynamicDecorationMap);
 | |
|                 breakAtStart = before.breakAtStart;
 | |
|                 openStart = before.openStart;
 | |
|                 openEnd = after.openEnd;
 | |
|                 let compLine = this.compositionView(composition);
 | |
|                 if (after.breakAtStart) {
 | |
|                     compLine.breakAfter = 1;
 | |
|                 }
 | |
|                 else if (after.content.length &&
 | |
|                     compLine.merge(compLine.length, compLine.length, after.content[0], false, after.openStart, 0)) {
 | |
|                     compLine.breakAfter = after.content[0].breakAfter;
 | |
|                     after.content.shift();
 | |
|                 }
 | |
|                 if (before.content.length &&
 | |
|                     compLine.merge(0, 0, before.content[before.content.length - 1], true, 0, before.openEnd)) {
 | |
|                     before.content.pop();
 | |
|                 }
 | |
|                 content = before.content.concat(compLine).concat(after.content);
 | |
|             }
 | |
|             else {
 | |
|                 ({ content, breakAtStart, openStart, openEnd } =
 | |
|                     ContentBuilder.build(this.view.state.doc, fromB, toB, this.decorations, this.dynamicDecorationMap));
 | |
|             }
 | |
|             let { i: toI, off: toOff } = cursor.findPos(toA, 1);
 | |
|             let { i: fromI, off: fromOff } = cursor.findPos(fromA, -1);
 | |
|             replaceRange(this, fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd);
 | |
|         }
 | |
|         if (composition)
 | |
|             this.fixCompositionDOM(composition);
 | |
|     }
 | |
|     updateEditContextFormatting(update) {
 | |
|         this.editContextFormatting = this.editContextFormatting.map(update.changes);
 | |
|         for (let tr of update.transactions)
 | |
|             for (let effect of tr.effects)
 | |
|                 if (effect.is(setEditContextFormatting)) {
 | |
|                     this.editContextFormatting = effect.value;
 | |
|                 }
 | |
|     }
 | |
|     compositionView(composition) {
 | |
|         let cur = new TextView(composition.text.nodeValue);
 | |
|         cur.flags |= 8 /* ViewFlag.Composition */;
 | |
|         for (let { deco } of composition.marks)
 | |
|             cur = new MarkView(deco, [cur], cur.length);
 | |
|         let line = new LineView;
 | |
|         line.append(cur, 0);
 | |
|         return line;
 | |
|     }
 | |
|     fixCompositionDOM(composition) {
 | |
|         let fix = (dom, cView) => {
 | |
|             cView.flags |= 8 /* ViewFlag.Composition */ | (cView.children.some(c => c.flags & 7 /* ViewFlag.Dirty */) ? 1 /* ViewFlag.ChildDirty */ : 0);
 | |
|             this.markedForComposition.add(cView);
 | |
|             let prev = ContentView.get(dom);
 | |
|             if (prev && prev != cView)
 | |
|                 prev.dom = null;
 | |
|             cView.setDOM(dom);
 | |
|         };
 | |
|         let pos = this.childPos(composition.range.fromB, 1);
 | |
|         let cView = this.children[pos.i];
 | |
|         fix(composition.line, cView);
 | |
|         for (let i = composition.marks.length - 1; i >= -1; i--) {
 | |
|             pos = cView.childPos(pos.off, 1);
 | |
|             cView = cView.children[pos.i];
 | |
|             fix(i >= 0 ? composition.marks[i].node : composition.text, cView);
 | |
|         }
 | |
|     }
 | |
|     // Sync the DOM selection to this.state.selection
 | |
|     updateSelection(mustRead = false, fromPointer = false) {
 | |
|         if (mustRead || !this.view.observer.selectionRange.focusNode)
 | |
|             this.view.observer.readSelectionRange();
 | |
|         let activeElt = this.view.root.activeElement, focused = activeElt == this.dom;
 | |
|         let selectionNotFocus = !focused &&
 | |
|             hasSelection(this.dom, this.view.observer.selectionRange) && !(activeElt && this.dom.contains(activeElt));
 | |
|         if (!(focused || fromPointer || selectionNotFocus))
 | |
|             return;
 | |
|         let force = this.forceSelection;
 | |
|         this.forceSelection = false;
 | |
|         let main = this.view.state.selection.main;
 | |
|         let anchor = this.moveToLine(this.domAtPos(main.anchor));
 | |
|         let head = main.empty ? anchor : this.moveToLine(this.domAtPos(main.head));
 | |
|         // Always reset on Firefox when next to an uneditable node to
 | |
|         // avoid invisible cursor bugs (#111)
 | |
|         if (browser.gecko && main.empty && !this.hasComposition && betweenUneditable(anchor)) {
 | |
|             let dummy = document.createTextNode("");
 | |
|             this.view.observer.ignore(() => anchor.node.insertBefore(dummy, anchor.node.childNodes[anchor.offset] || null));
 | |
|             anchor = head = new DOMPos(dummy, 0);
 | |
|             force = true;
 | |
|         }
 | |
|         let domSel = this.view.observer.selectionRange;
 | |
|         // If the selection is already here, or in an equivalent position, don't touch it
 | |
|         if (force || !domSel.focusNode || (!isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
 | |
|             !isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) && !this.suppressWidgetCursorChange(domSel, main)) {
 | |
|             this.view.observer.ignore(() => {
 | |
|                 // Chrome Android will hide the virtual keyboard when tapping
 | |
|                 // inside an uneditable node, and not bring it back when we
 | |
|                 // move the cursor to its proper position. This tries to
 | |
|                 // restore the keyboard by cycling focus.
 | |
|                 if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) &&
 | |
|                     inUneditable(domSel.focusNode, this.dom)) {
 | |
|                     this.dom.blur();
 | |
|                     this.dom.focus({ preventScroll: true });
 | |
|                 }
 | |
|                 let rawSel = getSelection(this.view.root);
 | |
|                 if (!rawSel) ;
 | |
|                 else if (main.empty) {
 | |
|                     // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
 | |
|                     if (browser.gecko) {
 | |
|                         let nextTo = nextToUneditable(anchor.node, anchor.offset);
 | |
|                         if (nextTo && nextTo != (1 /* NextTo.Before */ | 2 /* NextTo.After */)) {
 | |
|                             let text = (nextTo == 1 /* NextTo.Before */ ? textNodeBefore : textNodeAfter)(anchor.node, anchor.offset);
 | |
|                             if (text)
 | |
|                                 anchor = new DOMPos(text.node, text.offset);
 | |
|                         }
 | |
|                     }
 | |
|                     rawSel.collapse(anchor.node, anchor.offset);
 | |
|                     if (main.bidiLevel != null && rawSel.caretBidiLevel !== undefined)
 | |
|                         rawSel.caretBidiLevel = main.bidiLevel;
 | |
|                 }
 | |
|                 else if (rawSel.extend) {
 | |
|                     // Selection.extend can be used to create an 'inverted' selection
 | |
|                     // (one where the focus is before the anchor), but not all
 | |
|                     // browsers support it yet.
 | |
|                     rawSel.collapse(anchor.node, anchor.offset);
 | |
|                     // Safari will ignore the call above when the editor is
 | |
|                     // hidden, and then raise an error on the call to extend
 | |
|                     // (#940).
 | |
|                     try {
 | |
|                         rawSel.extend(head.node, head.offset);
 | |
|                     }
 | |
|                     catch (_) { }
 | |
|                 }
 | |
|                 else {
 | |
|                     // Primitive (IE) way
 | |
|                     let range = document.createRange();
 | |
|                     if (main.anchor > main.head)
 | |
|                         [anchor, head] = [head, anchor];
 | |
|                     range.setEnd(head.node, head.offset);
 | |
|                     range.setStart(anchor.node, anchor.offset);
 | |
|                     rawSel.removeAllRanges();
 | |
|                     rawSel.addRange(range);
 | |
|                 }
 | |
|                 if (selectionNotFocus && this.view.root.activeElement == this.dom) {
 | |
|                     this.dom.blur();
 | |
|                     if (activeElt)
 | |
|                         activeElt.focus();
 | |
|                 }
 | |
|             });
 | |
|             this.view.observer.setSelectionRange(anchor, head);
 | |
|         }
 | |
|         this.impreciseAnchor = anchor.precise ? null : new DOMPos(domSel.anchorNode, domSel.anchorOffset);
 | |
|         this.impreciseHead = head.precise ? null : new DOMPos(domSel.focusNode, domSel.focusOffset);
 | |
|     }
 | |
|     // If a zero-length widget is inserted next to the cursor during
 | |
|     // composition, avoid moving it across it and disrupting the
 | |
|     // composition.
 | |
|     suppressWidgetCursorChange(sel, cursor) {
 | |
|         return this.hasComposition && cursor.empty &&
 | |
|             isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset) &&
 | |
|             this.posFromDOM(sel.focusNode, sel.focusOffset) == cursor.head;
 | |
|     }
 | |
|     enforceCursorAssoc() {
 | |
|         if (this.hasComposition)
 | |
|             return;
 | |
|         let { view } = this, cursor = view.state.selection.main;
 | |
|         let sel = getSelection(view.root);
 | |
|         let { anchorNode, anchorOffset } = view.observer.selectionRange;
 | |
|         if (!sel || !cursor.empty || !cursor.assoc || !sel.modify)
 | |
|             return;
 | |
|         let line = LineView.find(this, cursor.head);
 | |
|         if (!line)
 | |
|             return;
 | |
|         let lineStart = line.posAtStart;
 | |
|         if (cursor.head == lineStart || cursor.head == lineStart + line.length)
 | |
|             return;
 | |
|         let before = this.coordsAt(cursor.head, -1), after = this.coordsAt(cursor.head, 1);
 | |
|         if (!before || !after || before.bottom > after.top)
 | |
|             return;
 | |
|         let dom = this.domAtPos(cursor.head + cursor.assoc);
 | |
|         sel.collapse(dom.node, dom.offset);
 | |
|         sel.modify("move", cursor.assoc < 0 ? "forward" : "backward", "lineboundary");
 | |
|         // This can go wrong in corner cases like single-character lines,
 | |
|         // so check and reset if necessary.
 | |
|         view.observer.readSelectionRange();
 | |
|         let newRange = view.observer.selectionRange;
 | |
|         if (view.docView.posFromDOM(newRange.anchorNode, newRange.anchorOffset) != cursor.from)
 | |
|             sel.collapse(anchorNode, anchorOffset);
 | |
|     }
 | |
|     // If a position is in/near a block widget, move it to a nearby text
 | |
|     // line, since we don't want the cursor inside a block widget.
 | |
|     moveToLine(pos) {
 | |
|         // Block widgets will return positions before/after them, which
 | |
|         // are thus directly in the document DOM element.
 | |
|         let dom = this.dom, newPos;
 | |
|         if (pos.node != dom)
 | |
|             return pos;
 | |
|         for (let i = pos.offset; !newPos && i < dom.childNodes.length; i++) {
 | |
|             let view = ContentView.get(dom.childNodes[i]);
 | |
|             if (view instanceof LineView)
 | |
|                 newPos = view.domAtPos(0);
 | |
|         }
 | |
|         for (let i = pos.offset - 1; !newPos && i >= 0; i--) {
 | |
|             let view = ContentView.get(dom.childNodes[i]);
 | |
|             if (view instanceof LineView)
 | |
|                 newPos = view.domAtPos(view.length);
 | |
|         }
 | |
|         return newPos ? new DOMPos(newPos.node, newPos.offset, true) : pos;
 | |
|     }
 | |
|     nearest(dom) {
 | |
|         for (let cur = dom; cur;) {
 | |
|             let domView = ContentView.get(cur);
 | |
|             if (domView && domView.rootView == this)
 | |
|                 return domView;
 | |
|             cur = cur.parentNode;
 | |
|         }
 | |
|         return null;
 | |
|     }
 | |
|     posFromDOM(node, offset) {
 | |
|         let view = this.nearest(node);
 | |
|         if (!view)
 | |
|             throw new RangeError("Trying to find position for a DOM position outside of the document");
 | |
|         return view.localPosFromDOM(node, offset) + view.posAtStart;
 | |
|     }
 | |
|     domAtPos(pos) {
 | |
|         let { i, off } = this.childCursor().findPos(pos, -1);
 | |
|         for (; i < this.children.length - 1;) {
 | |
|             let child = this.children[i];
 | |
|             if (off < child.length || child instanceof LineView)
 | |
|                 break;
 | |
|             i++;
 | |
|             off = 0;
 | |
|         }
 | |
|         return this.children[i].domAtPos(off);
 | |
|     }
 | |
|     coordsAt(pos, side) {
 | |
|         let best = null, bestPos = 0;
 | |
|         for (let off = this.length, i = this.children.length - 1; i >= 0; i--) {
 | |
|             let child = this.children[i], end = off - child.breakAfter, start = end - child.length;
 | |
|             if (end < pos)
 | |
|                 break;
 | |
|             if (start <= pos && (start < pos || child.covers(-1)) && (end > pos || child.covers(1)) &&
 | |
|                 (!best || child instanceof LineView && !(best instanceof LineView && side >= 0))) {
 | |
|                 best = child;
 | |
|                 bestPos = start;
 | |
|             }
 | |
|             else if (best && start == pos && end == pos && child instanceof BlockWidgetView && Math.abs(side) < 2) {
 | |
|                 if (child.deco.startSide < 0)
 | |
|                     break;
 | |
|                 else if (i)
 | |
|                     best = null;
 | |
|             }
 | |
|             off = start;
 | |
|         }
 | |
|         return best ? best.coordsAt(pos - bestPos, side) : null;
 | |
|     }
 | |
|     coordsForChar(pos) {
 | |
|         let { i, off } = this.childPos(pos, 1), child = this.children[i];
 | |
|         if (!(child instanceof LineView))
 | |
|             return null;
 | |
|         while (child.children.length) {
 | |
|             let { i, off: childOff } = child.childPos(off, 1);
 | |
|             for (;; i++) {
 | |
|                 if (i == child.children.length)
 | |
|                     return null;
 | |
|                 if ((child = child.children[i]).length)
 | |
|                     break;
 | |
|             }
 | |
|             off = childOff;
 | |
|         }
 | |
|         if (!(child instanceof TextView))
 | |
|             return null;
 | |
|         let end = findClusterBreak(child.text, off);
 | |
|         if (end == off)
 | |
|             return null;
 | |
|         let rects = textRange(child.dom, off, end).getClientRects();
 | |
|         for (let i = 0; i < rects.length; i++) {
 | |
|             let rect = rects[i];
 | |
|             if (i == rects.length - 1 || rect.top < rect.bottom && rect.left < rect.right)
 | |
|                 return rect;
 | |
|         }
 | |
|         return null;
 | |
|     }
 | |
|     measureVisibleLineHeights(viewport) {
 | |
|         let result = [], { from, to } = viewport;
 | |
|         let contentWidth = this.view.contentDOM.clientWidth;
 | |
|         let isWider = contentWidth > Math.max(this.view.scrollDOM.clientWidth, this.minWidth) + 1;
 | |
|         let widest = -1, ltr = this.view.textDirection == Direction.LTR;
 | |
|         for (let pos = 0, i = 0; i < this.children.length; i++) {
 | |
|             let child = this.children[i], end = pos + child.length;
 | |
|             if (end > to)
 | |
|                 break;
 | |
|             if (pos >= from) {
 | |
|                 let childRect = child.dom.getBoundingClientRect();
 | |
|                 result.push(childRect.height);
 | |
|                 if (isWider) {
 | |
|                     let last = child.dom.lastChild;
 | |
|                     let rects = last ? clientRectsFor(last) : [];
 | |
|                     if (rects.length) {
 | |
|                         let rect = rects[rects.length - 1];
 | |
|                         let width = ltr ? rect.right - childRect.left : childRect.right - rect.left;
 | |
|                         if (width > widest) {
 | |
|                             widest = width;
 | |
|                             this.minWidth = contentWidth;
 | |
|                             this.minWidthFrom = pos;
 | |
|                             this.minWidthTo = end;
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             pos = end + child.breakAfter;
 | |
|         }
 | |
|         return result;
 | |
|     }
 | |
|     textDirectionAt(pos) {
 | |
|         let { i } = this.childPos(pos, 1);
 | |
|         return getComputedStyle(this.children[i].dom).direction == "rtl" ? Direction.RTL : Direction.LTR;
 | |
|     }
 | |
|     measureTextSize() {
 | |
|         for (let child of this.children) {
 | |
|             if (child instanceof LineView) {
 | |
|                 let measure = child.measureTextSize();
 | |
|                 if (measure)
 | |
|                     return measure;
 | |
|             }
 | |
|         }
 | |
|         // If no workable line exists, force a layout of a measurable element
 | |
|         let dummy = document.createElement("div"), lineHeight, charWidth, textHeight;
 | |
|         dummy.className = "cm-line";
 | |
|         dummy.style.width = "99999px";
 | |
|         dummy.style.position = "absolute";
 | |
|         dummy.textContent = "abc def ghi jkl mno pqr stu";
 | |
|         this.view.observer.ignore(() => {
 | |
|             this.dom.appendChild(dummy);
 | |
|             let rect = clientRectsFor(dummy.firstChild)[0];
 | |
|             lineHeight = dummy.getBoundingClientRect().height;
 | |
|             charWidth = rect ? rect.width / 27 : 7;
 | |
|             textHeight = rect ? rect.height : lineHeight;
 | |
|             dummy.remove();
 | |
|         });
 | |
|         return { lineHeight, charWidth, textHeight };
 | |
|     }
 | |
|     childCursor(pos = this.length) {
 | |
|         // Move back to start of last element when possible, so that
 | |
|         // `ChildCursor.findPos` doesn't have to deal with the edge case
 | |
|         // of being after the last element.
 | |
|         let i = this.children.length;
 | |
|         if (i)
 | |
|             pos -= this.children[--i].length;
 | |
|         return new ChildCursor(this.children, pos, i);
 | |
|     }
 | |
|     computeBlockGapDeco() {
 | |
|         let deco = [], vs = this.view.viewState;
 | |
|         for (let pos = 0, i = 0;; i++) {
 | |
|             let next = i == vs.viewports.length ? null : vs.viewports[i];
 | |
|             let end = next ? next.from - 1 : this.length;
 | |
|             if (end > pos) {
 | |
|                 let height = (vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top) / this.view.scaleY;
 | |
|                 deco.push(Decoration.replace({
 | |
|                     widget: new BlockGapWidget(height),
 | |
|                     block: true,
 | |
|                     inclusive: true,
 | |
|                     isBlockGap: true,
 | |
|                 }).range(pos, end));
 | |
|             }
 | |
|             if (!next)
 | |
|                 break;
 | |
|             pos = next.to + 1;
 | |
|         }
 | |
|         return Decoration.set(deco);
 | |
|     }
 | |
|     updateDeco() {
 | |
|         let i = 1;
 | |
|         let allDeco = this.view.state.facet(decorations).map(d => {
 | |
|             let dynamic = this.dynamicDecorationMap[i++] = typeof d == "function";
 | |
|             return dynamic ? d(this.view) : d;
 | |
|         });
 | |
|         let dynamicOuter = false, outerDeco = this.view.state.facet(outerDecorations).map((d, i) => {
 | |
|             let dynamic = typeof d == "function";
 | |
|             if (dynamic)
 | |
|                 dynamicOuter = true;
 | |
|             return dynamic ? d(this.view) : d;
 | |
|         });
 | |
|         if (outerDeco.length) {
 | |
|             this.dynamicDecorationMap[i++] = dynamicOuter;
 | |
|             allDeco.push(RangeSet.join(outerDeco));
 | |
|         }
 | |
|         this.decorations = [
 | |
|             this.editContextFormatting,
 | |
|             ...allDeco,
 | |
|             this.computeBlockGapDeco(),
 | |
|             this.view.viewState.lineGapDeco
 | |
|         ];
 | |
|         while (i < this.decorations.length)
 | |
|             this.dynamicDecorationMap[i++] = false;
 | |
|         return this.decorations;
 | |
|     }
 | |
|     scrollIntoView(target) {
 | |
|         if (target.isSnapshot) {
 | |
|             let ref = this.view.viewState.lineBlockAt(target.range.head);
 | |
|             this.view.scrollDOM.scrollTop = ref.top - target.yMargin;
 | |
|             this.view.scrollDOM.scrollLeft = target.xMargin;
 | |
|             return;
 | |
|         }
 | |
|         for (let handler of this.view.state.facet(scrollHandler)) {
 | |
|             try {
 | |
|                 if (handler(this.view, target.range, target))
 | |
|                     return true;
 | |
|             }
 | |
|             catch (e) {
 | |
|                 logException(this.view.state, e, "scroll handler");
 | |
|             }
 | |
|         }
 | |
|         let { range } = target;
 | |
|         let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
 | |
|         if (!rect)
 | |
|             return;
 | |
|         if (!range.empty && (other = this.coordsAt(range.anchor, range.anchor > range.head ? -1 : 1)))
 | |
|             rect = { left: Math.min(rect.left, other.left), top: Math.min(rect.top, other.top),
 | |
|                 right: Math.max(rect.right, other.right), bottom: Math.max(rect.bottom, other.bottom) };
 | |
|         let margins = getScrollMargins(this.view);
 | |
|         let targetRect = {
 | |
|             left: rect.left - margins.left, top: rect.top - margins.top,
 | |
|             right: rect.right + margins.right, bottom: rect.bottom + margins.bottom
 | |
|         };
 | |
|         let { offsetWidth, offsetHeight } = this.view.scrollDOM;
 | |
|         scrollRectIntoView(this.view.scrollDOM, targetRect, range.head < range.anchor ? -1 : 1, target.x, target.y, Math.max(Math.min(target.xMargin, offsetWidth), -offsetWidth), Math.max(Math.min(target.yMargin, offsetHeight), -offsetHeight), this.view.textDirection == Direction.LTR);
 | |
|     }
 | |
| }
 | |
| function betweenUneditable(pos) {
 | |
|     return pos.node.nodeType == 1 && pos.node.firstChild &&
 | |
|         (pos.offset == 0 || pos.node.childNodes[pos.offset - 1].contentEditable == "false") &&
 | |
|         (pos.offset == pos.node.childNodes.length || pos.node.childNodes[pos.offset].contentEditable == "false");
 | |
| }
 | |
| function findCompositionNode(view, headPos) {
 | |
|     let sel = view.observer.selectionRange;
 | |
|     if (!sel.focusNode)
 | |
|         return null;
 | |
|     let textBefore = textNodeBefore(sel.focusNode, sel.focusOffset);
 | |
|     let textAfter = textNodeAfter(sel.focusNode, sel.focusOffset);
 | |
|     let textNode = textBefore || textAfter;
 | |
|     if (textAfter && textBefore && textAfter.node != textBefore.node) {
 | |
|         let descAfter = ContentView.get(textAfter.node);
 | |
|         if (!descAfter || descAfter instanceof TextView && descAfter.text != textAfter.node.nodeValue) {
 | |
|             textNode = textAfter;
 | |
|         }
 | |
|         else if (view.docView.lastCompositionAfterCursor) {
 | |
|             let descBefore = ContentView.get(textBefore.node);
 | |
|             if (!(!descBefore || descBefore instanceof TextView && descBefore.text != textBefore.node.nodeValue))
 | |
|                 textNode = textAfter;
 | |
|         }
 | |
|     }
 | |
|     view.docView.lastCompositionAfterCursor = textNode != textBefore;
 | |
|     if (!textNode)
 | |
|         return null;
 | |
|     let from = headPos - textNode.offset;
 | |
|     return { from, to: from + textNode.node.nodeValue.length, node: textNode.node };
 | |
| }
 | |
| function findCompositionRange(view, changes, headPos) {
 | |
|     let found = findCompositionNode(view, headPos);
 | |
|     if (!found)
 | |
|         return null;
 | |
|     let { node: textNode, from, to } = found, text = textNode.nodeValue;
 | |
|     // Don't try to preserve multi-line compositions
 | |
|     if (/[\n\r]/.test(text))
 | |
|         return null;
 | |
|     if (view.state.doc.sliceString(found.from, found.to) != text)
 | |
|         return null;
 | |
|     let inv = changes.invertedDesc;
 | |
|     let range = new ChangedRange(inv.mapPos(from), inv.mapPos(to), from, to);
 | |
|     let marks = [];
 | |
|     for (let parent = textNode.parentNode;; parent = parent.parentNode) {
 | |
|         let parentView = ContentView.get(parent);
 | |
|         if (parentView instanceof MarkView)
 | |
|             marks.push({ node: parent, deco: parentView.mark });
 | |
|         else if (parentView instanceof LineView || parent.nodeName == "DIV" && parent.parentNode == view.contentDOM)
 | |
|             return { range, text: textNode, marks, line: parent };
 | |
|         else if (parent != view.contentDOM)
 | |
|             marks.push({ node: parent, deco: new MarkDecoration({
 | |
|                     inclusive: true,
 | |
|                     attributes: getAttrs(parent),
 | |
|                     tagName: parent.tagName.toLowerCase()
 | |
|                 }) });
 | |
|         else
 | |
|             return null;
 | |
|     }
 | |
| }
 | |
| function nextToUneditable(node, offset) {
 | |
|     if (node.nodeType != 1)
 | |
|         return 0;
 | |
|     return (offset && node.childNodes[offset - 1].contentEditable == "false" ? 1 /* NextTo.Before */ : 0) |
 | |
|         (offset < node.childNodes.length && node.childNodes[offset].contentEditable == "false" ? 2 /* NextTo.After */ : 0);
 | |
| }
 | |
| let DecorationComparator$1 = class DecorationComparator {
 | |
|     constructor() {
 | |
|         this.changes = [];
 | |
|     }
 | |
|     compareRange(from, to) { addRange(from, to, this.changes); }
 | |
|     comparePoint(from, to) { addRange(from, to, this.changes); }
 | |
| };
 | |
| function findChangedDeco(a, b, diff) {
 | |
|     let comp = new DecorationComparator$1;
 | |
|     RangeSet.compare(a, b, diff, comp);
 | |
|     return comp.changes;
 | |
| }
 | |
| function inUneditable(node, inside) {
 | |
|     for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
 | |
|         if (cur.nodeType == 1 && cur.contentEditable == 'false') {
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
|     return false;
 | |
| }
 | |
| function touchesComposition(changes, composition) {
 | |
|     let touched = false;
 | |
|     if (composition)
 | |
|         changes.iterChangedRanges((from, to) => {
 | |
|             if (from < composition.to && to > composition.from)
 | |
|                 touched = true;
 | |
|         });
 | |
|     return touched;
 | |
| }
 | |
| 
 | |
| function groupAt(state, pos, bias = 1) {
 | |
|     let categorize = state.charCategorizer(pos);
 | |
|     let line = state.doc.lineAt(pos), linePos = pos - line.from;
 | |
|     if (line.length == 0)
 | |
|         return EditorSelection.cursor(pos);
 | |
|     if (linePos == 0)
 | |
|         bias = 1;
 | |
|     else if (linePos == line.length)
 | |
|         bias = -1;
 | |
|     let from = linePos, to = linePos;
 | |
|     if (bias < 0)
 | |
|         from = findClusterBreak(line.text, linePos, false);
 | |
|     else
 | |
|         to = findClusterBreak(line.text, linePos);
 | |
|     let cat = categorize(line.text.slice(from, to));
 | |
|     while (from > 0) {
 | |
|         let prev = findClusterBreak(line.text, from, false);
 | |
|         if (categorize(line.text.slice(prev, from)) != cat)
 | |
|             break;
 | |
|         from = prev;
 | |
|     }
 | |
|     while (to < line.length) {
 | |
|         let next = findClusterBreak(line.text, to);
 | |
|         if (categorize(line.text.slice(to, next)) != cat)
 | |
|             break;
 | |
|         to = next;
 | |
|     }
 | |
|     return EditorSelection.range(from + line.from, to + line.from);
 | |
| }
 | |
| // Search the DOM for the {node, offset} position closest to the given
 | |
| // coordinates. Very inefficient and crude, but can usually be avoided
 | |
| // by calling caret(Position|Range)FromPoint instead.
 | |
| function getdx(x, rect) {
 | |
|     return rect.left > x ? rect.left - x : Math.max(0, x - rect.right);
 | |
| }
 | |
| function getdy(y, rect) {
 | |
|     return rect.top > y ? rect.top - y : Math.max(0, y - rect.bottom);
 | |
| }
 | |
| function yOverlap(a, b) {
 | |
|     return a.top < b.bottom - 1 && a.bottom > b.top + 1;
 | |
| }
 | |
| function upTop(rect, top) {
 | |
|     return top < rect.top ? { top, left: rect.left, right: rect.right, bottom: rect.bottom } : rect;
 | |
| }
 | |
| function upBot(rect, bottom) {
 | |
|     return bottom > rect.bottom ? { top: rect.top, left: rect.left, right: rect.right, bottom } : rect;
 | |
| }
 | |
| function domPosAtCoords(parent, x, y) {
 | |
|     let closest, closestRect, closestX, closestY, closestOverlap = false;
 | |
|     let above, below, aboveRect, belowRect;
 | |
|     for (let child = parent.firstChild; child; child = child.nextSibling) {
 | |
|         let rects = clientRectsFor(child);
 | |
|         for (let i = 0; i < rects.length; i++) {
 | |
|             let rect = rects[i];
 | |
|             if (closestRect && yOverlap(closestRect, rect))
 | |
|                 rect = upTop(upBot(rect, closestRect.bottom), closestRect.top);
 | |
|             let dx = getdx(x, rect), dy = getdy(y, rect);
 | |
|             if (dx == 0 && dy == 0)
 | |
|                 return child.nodeType == 3 ? domPosInText(child, x, y) : domPosAtCoords(child, x, y);
 | |
|             if (!closest || closestY > dy || closestY == dy && closestX > dx) {
 | |
|                 closest = child;
 | |
|                 closestRect = rect;
 | |
|                 closestX = dx;
 | |
|                 closestY = dy;
 | |
|                 let side = dy ? (y < rect.top ? -1 : 1) : dx ? (x < rect.left ? -1 : 1) : 0;
 | |
|                 closestOverlap = !side || (side > 0 ? i < rects.length - 1 : i > 0);
 | |
|             }
 | |
|             if (dx == 0) {
 | |
|                 if (y > rect.bottom && (!aboveRect || aboveRect.bottom < rect.bottom)) {
 | |
|                     above = child;
 | |
|                     aboveRect = rect;
 | |
|                 }
 | |
|                 else if (y < rect.top && (!belowRect || belowRect.top > rect.top)) {
 | |
|                     below = child;
 | |
|                     belowRect = rect;
 | |
|                 }
 | |
|             }
 | |
|             else if (aboveRect && yOverlap(aboveRect, rect)) {
 | |
|                 aboveRect = upBot(aboveRect, rect.bottom);
 | |
|             }
 | |
|             else if (belowRect && yOverlap(belowRect, rect)) {
 | |
|                 belowRect = upTop(belowRect, rect.top);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     if (aboveRect && aboveRect.bottom >= y) {
 | |
|         closest = above;
 | |
|         closestRect = aboveRect;
 | |
|     }
 | |
|     else if (belowRect && belowRect.top <= y) {
 | |
|         closest = below;
 | |
|         closestRect = belowRect;
 | |
|     }
 | |
|     if (!closest)
 | |
|         return { node: parent, offset: 0 };
 | |
|     let clipX = Math.max(closestRect.left, Math.min(closestRect.right, x));
 | |
|     if (closest.nodeType == 3)
 | |
|         return domPosInText(closest, clipX, y);
 | |
|     if (closestOverlap && closest.contentEditable != "false")
 | |
|         return domPosAtCoords(closest, clipX, y);
 | |
|     let offset = Array.prototype.indexOf.call(parent.childNodes, closest) +
 | |
|         (x >= (closestRect.left + closestRect.right) / 2 ? 1 : 0);
 | |
|     return { node: parent, offset };
 | |
| }
 | |
| function domPosInText(node, x, y) {
 | |
|     let len = node.nodeValue.length;
 | |
|     let closestOffset = -1, closestDY = 1e9, generalSide = 0;
 | |
|     for (let i = 0; i < len; i++) {
 | |
|         let rects = textRange(node, i, i + 1).getClientRects();
 | |
|         for (let j = 0; j < rects.length; j++) {
 | |
|             let rect = rects[j];
 | |
|             if (rect.top == rect.bottom)
 | |
|                 continue;
 | |
|             if (!generalSide)
 | |
|                 generalSide = x - rect.left;
 | |
|             let dy = (rect.top > y ? rect.top - y : y - rect.bottom) - 1;
 | |
|             if (rect.left - 1 <= x && rect.right + 1 >= x && dy < closestDY) {
 | |
|                 let right = x >= (rect.left + rect.right) / 2, after = right;
 | |
|                 if (browser.chrome || browser.gecko) {
 | |
|                     // Check for RTL on browsers that support getting client
 | |
|                     // rects for empty ranges.
 | |
|                     let rectBefore = textRange(node, i).getBoundingClientRect();
 | |
|                     if (rectBefore.left == rect.right)
 | |
|                         after = !right;
 | |
|                 }
 | |
|                 if (dy <= 0)
 | |
|                     return { node, offset: i + (after ? 1 : 0) };
 | |
|                 closestOffset = i + (after ? 1 : 0);
 | |
|                 closestDY = dy;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     return { node, offset: closestOffset > -1 ? closestOffset : generalSide > 0 ? node.nodeValue.length : 0 };
 | |
| }
 | |
| function posAtCoords(view, coords, precise, bias = -1) {
 | |
|     var _a, _b;
 | |
|     let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
 | |
|     let block, { docHeight } = view.viewState;
 | |
|     let { x, y } = coords, yOffset = y - docTop;
 | |
|     if (yOffset < 0)
 | |
|         return 0;
 | |
|     if (yOffset > docHeight)
 | |
|         return view.state.doc.length;
 | |
|     // Scan for a text block near the queried y position
 | |
|     for (let halfLine = view.viewState.heightOracle.textHeight / 2, bounced = false;;) {
 | |
|         block = view.elementAtHeight(yOffset);
 | |
|         if (block.type == BlockType.Text)
 | |
|             break;
 | |
|         for (;;) {
 | |
|             // Move the y position out of this block
 | |
|             yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
 | |
|             if (yOffset >= 0 && yOffset <= docHeight)
 | |
|                 break;
 | |
|             // If the document consists entirely of replaced widgets, we
 | |
|             // won't find a text block, so return 0
 | |
|             if (bounced)
 | |
|                 return precise ? null : 0;
 | |
|             bounced = true;
 | |
|             bias = -bias;
 | |
|         }
 | |
|     }
 | |
|     y = docTop + yOffset;
 | |
|     let lineStart = block.from;
 | |
|     // If this is outside of the rendered viewport, we can't determine a position
 | |
|     if (lineStart < view.viewport.from)
 | |
|         return view.viewport.from == 0 ? 0 : precise ? null : posAtCoordsImprecise(view, content, block, x, y);
 | |
|     if (lineStart > view.viewport.to)
 | |
|         return view.viewport.to == view.state.doc.length ? view.state.doc.length :
 | |
|             precise ? null : posAtCoordsImprecise(view, content, block, x, y);
 | |
|     // Prefer ShadowRootOrDocument.elementFromPoint if present, fall back to document if not
 | |
|     let doc = view.dom.ownerDocument;
 | |
|     let root = view.root.elementFromPoint ? view.root : doc;
 | |
|     let element = root.elementFromPoint(x, y);
 | |
|     if (element && !view.contentDOM.contains(element))
 | |
|         element = null;
 | |
|     // If the element is unexpected, clip x at the sides of the content area and try again
 | |
|     if (!element) {
 | |
|         x = Math.max(content.left + 1, Math.min(content.right - 1, x));
 | |
|         element = root.elementFromPoint(x, y);
 | |
|         if (element && !view.contentDOM.contains(element))
 | |
|             element = null;
 | |
|     }
 | |
|     // There's visible editor content under the point, so we can try
 | |
|     // using caret(Position|Range)FromPoint as a shortcut
 | |
|     let node, offset = -1;
 | |
|     if (element && ((_a = view.docView.nearest(element)) === null || _a === void 0 ? void 0 : _a.isEditable) != false) {
 | |
|         if (doc.caretPositionFromPoint) {
 | |
|             let pos = doc.caretPositionFromPoint(x, y);
 | |
|             if (pos)
 | |
|                 ({ offsetNode: node, offset } = pos);
 | |
|         }
 | |
|         else if (doc.caretRangeFromPoint) {
 | |
|             let range = doc.caretRangeFromPoint(x, y);
 | |
|             if (range) {
 | |
|                 ({ startContainer: node, startOffset: offset } = range);
 | |
|                 if (!view.contentDOM.contains(node) ||
 | |
|                     browser.safari && isSuspiciousSafariCaretResult(node, offset, x) ||
 | |
|                     browser.chrome && isSuspiciousChromeCaretResult(node, offset, x))
 | |
|                     node = undefined;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     // No luck, do our own (potentially expensive) search
 | |
|     if (!node || !view.docView.dom.contains(node)) {
 | |
|         let line = LineView.find(view.docView, lineStart);
 | |
|         if (!line)
 | |
|             return yOffset > block.top + block.height / 2 ? block.to : block.from;
 | |
|         ({ node, offset } = domPosAtCoords(line.dom, x, y));
 | |
|     }
 | |
|     let nearest = view.docView.nearest(node);
 | |
|     if (!nearest)
 | |
|         return null;
 | |
|     if (nearest.isWidget && ((_b = nearest.dom) === null || _b === void 0 ? void 0 : _b.nodeType) == 1) {
 | |
|         let rect = nearest.dom.getBoundingClientRect();
 | |
|         return coords.y < rect.top || coords.y <= rect.bottom && coords.x <= (rect.left + rect.right) / 2
 | |
|             ? nearest.posAtStart : nearest.posAtEnd;
 | |
|     }
 | |
|     else {
 | |
|         return nearest.localPosFromDOM(node, offset) + nearest.posAtStart;
 | |
|     }
 | |
| }
 | |
| function posAtCoordsImprecise(view, contentRect, block, x, y) {
 | |
|     let into = Math.round((x - contentRect.left) * view.defaultCharacterWidth);
 | |
|     if (view.lineWrapping && block.height > view.defaultLineHeight * 1.5) {
 | |
|         let textHeight = view.viewState.heightOracle.textHeight;
 | |
|         let line = Math.floor((y - block.top - (view.defaultLineHeight - textHeight) * 0.5) / textHeight);
 | |
|         into += line * view.viewState.heightOracle.lineLength;
 | |
|     }
 | |
|     let content = view.state.sliceDoc(block.from, block.to);
 | |
|     return block.from + findColumn(content, into, view.state.tabSize);
 | |
| }
 | |
| // In case of a high line height, Safari's caretRangeFromPoint treats
 | |
| // the space between lines as belonging to the last character of the
 | |
| // line before. This is used to detect such a result so that it can be
 | |
| // ignored (issue #401).
 | |
| function isSuspiciousSafariCaretResult(node, offset, x) {
 | |
|     let len;
 | |
|     if (node.nodeType != 3 || offset != (len = node.nodeValue.length))
 | |
|         return false;
 | |
|     for (let next = node.nextSibling; next; next = next.nextSibling)
 | |
|         if (next.nodeType != 1 || next.nodeName != "BR")
 | |
|             return false;
 | |
|     return textRange(node, len - 1, len).getBoundingClientRect().left > x;
 | |
| }
 | |
| // Chrome will move positions between lines to the start of the next line
 | |
| function isSuspiciousChromeCaretResult(node, offset, x) {
 | |
|     if (offset != 0)
 | |
|         return false;
 | |
|     for (let cur = node;;) {
 | |
|         let parent = cur.parentNode;
 | |
|         if (!parent || parent.nodeType != 1 || parent.firstChild != cur)
 | |
|             return false;
 | |
|         if (parent.classList.contains("cm-line"))
 | |
|             break;
 | |
|         cur = parent;
 | |
|     }
 | |
|     let rect = node.nodeType == 1 ? node.getBoundingClientRect()
 | |
|         : textRange(node, 0, Math.max(node.nodeValue.length, 1)).getBoundingClientRect();
 | |
|     return x - rect.left > 5;
 | |
| }
 | |
| function blockAt(view, pos) {
 | |
|     let line = view.lineBlockAt(pos);
 | |
|     if (Array.isArray(line.type))
 | |
|         for (let l of line.type) {
 | |
|             if (l.to > pos || l.to == pos && (l.to == line.to || l.type == BlockType.Text))
 | |
|                 return l;
 | |
|         }
 | |
|     return line;
 | |
| }
 | |
| function moveToLineBoundary(view, start, forward, includeWrap) {
 | |
|     let line = blockAt(view, start.head);
 | |
|     let coords = !includeWrap || line.type != BlockType.Text || !(view.lineWrapping || line.widgetLineBreaks) ? null
 | |
|         : view.coordsAtPos(start.assoc < 0 && start.head > line.from ? start.head - 1 : start.head);
 | |
|     if (coords) {
 | |
|         let editorRect = view.dom.getBoundingClientRect();
 | |
|         let direction = view.textDirectionAt(line.from);
 | |
|         let pos = view.posAtCoords({ x: forward == (direction == Direction.LTR) ? editorRect.right - 1 : editorRect.left + 1,
 | |
|             y: (coords.top + coords.bottom) / 2 });
 | |
|         if (pos != null)
 | |
|             return EditorSelection.cursor(pos, forward ? -1 : 1);
 | |
|     }
 | |
|     return EditorSelection.cursor(forward ? line.to : line.from, forward ? -1 : 1);
 | |
| }
 | |
| function moveByChar(view, start, forward, by) {
 | |
|     let line = view.state.doc.lineAt(start.head), spans = view.bidiSpans(line);
 | |
|     let direction = view.textDirectionAt(line.from);
 | |
|     for (let cur = start, check = null;;) {
 | |
|         let next = moveVisually(line, spans, direction, cur, forward), char = movedOver;
 | |
|         if (!next) {
 | |
|             if (line.number == (forward ? view.state.doc.lines : 1))
 | |
|                 return cur;
 | |
|             char = "\n";
 | |
|             line = view.state.doc.line(line.number + (forward ? 1 : -1));
 | |
|             spans = view.bidiSpans(line);
 | |
|             next = view.visualLineSide(line, !forward);
 | |
|         }
 | |
|         if (!check) {
 | |
|             if (!by)
 | |
|                 return next;
 | |
|             check = by(char);
 | |
|         }
 | |
|         else if (!check(char)) {
 | |
|             return cur;
 | |
|         }
 | |
|         cur = next;
 | |
|     }
 | |
| }
 | |
| function byGroup(view, pos, start) {
 | |
|     let categorize = view.state.charCategorizer(pos);
 | |
|     let cat = categorize(start);
 | |
|     return (next) => {
 | |
|         let nextCat = categorize(next);
 | |
|         if (cat == CharCategory.Space)
 | |
|             cat = nextCat;
 | |
|         return cat == nextCat;
 | |
|     };
 | |
| }
 | |
| function moveVertically(view, start, forward, distance) {
 | |
|     let startPos = start.head, dir = forward ? 1 : -1;
 | |
|     if (startPos == (forward ? view.state.doc.length : 0))
 | |
|         return EditorSelection.cursor(startPos, start.assoc);
 | |
|     let goal = start.goalColumn, startY;
 | |
|     let rect = view.contentDOM.getBoundingClientRect();
 | |
|     let startCoords = view.coordsAtPos(startPos, start.assoc || -1), docTop = view.documentTop;
 | |
|     if (startCoords) {
 | |
|         if (goal == null)
 | |
|             goal = startCoords.left - rect.left;
 | |
|         startY = dir < 0 ? startCoords.top : startCoords.bottom;
 | |
|     }
 | |
|     else {
 | |
|         let line = view.viewState.lineBlockAt(startPos);
 | |
|         if (goal == null)
 | |
|             goal = Math.min(rect.right - rect.left, view.defaultCharacterWidth * (startPos - line.from));
 | |
|         startY = (dir < 0 ? line.top : line.bottom) + docTop;
 | |
|     }
 | |
|     let resolvedGoal = rect.left + goal;
 | |
|     let dist = distance !== null && distance !== void 0 ? distance : (view.viewState.heightOracle.textHeight >> 1);
 | |
|     for (let extra = 0;; extra += 10) {
 | |
|         let curY = startY + (dist + extra) * dir;
 | |
|         let pos = posAtCoords(view, { x: resolvedGoal, y: curY }, false, dir);
 | |
|         if (curY < rect.top || curY > rect.bottom || (dir < 0 ? pos < startPos : pos > startPos)) {
 | |
|             let charRect = view.docView.coordsForChar(pos);
 | |
|             let assoc = !charRect || curY < charRect.top ? -1 : 1;
 | |
|             return EditorSelection.cursor(pos, assoc, undefined, goal);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function skipAtomicRanges(atoms, pos, bias) {
 | |
|     for (;;) {
 | |
|         let moved = 0;
 | |
|         for (let set of atoms) {
 | |
|             set.between(pos - 1, pos + 1, (from, to, value) => {
 | |
|                 if (pos > from && pos < to) {
 | |
|                     let side = moved || bias || (pos - from < to - pos ? -1 : 1);
 | |
|                     pos = side < 0 ? from : to;
 | |
|                     moved = side;
 | |
|                 }
 | |
|             });
 | |
|         }
 | |
|         if (!moved)
 | |
|             return pos;
 | |
|     }
 | |
| }
 | |
| function skipAtoms(view, oldPos, pos) {
 | |
|     let newPos = skipAtomicRanges(view.state.facet(atomicRanges).map(f => f(view)), pos.from, oldPos.head > pos.from ? -1 : 1);
 | |
|     return newPos == pos.from ? pos : EditorSelection.cursor(newPos, newPos < pos.from ? 1 : -1);
 | |
| }
 | |
| 
 | |
| const LineBreakPlaceholder = "\uffff";
 | |
| class DOMReader {
 | |
|     constructor(points, state) {
 | |
|         this.points = points;
 | |
|         this.text = "";
 | |
|         this.lineSeparator = state.facet(EditorState.lineSeparator);
 | |
|     }
 | |
|     append(text) {
 | |
|         this.text += text;
 | |
|     }
 | |
|     lineBreak() {
 | |
|         this.text += LineBreakPlaceholder;
 | |
|     }
 | |
|     readRange(start, end) {
 | |
|         if (!start)
 | |
|             return this;
 | |
|         let parent = start.parentNode;
 | |
|         for (let cur = start;;) {
 | |
|             this.findPointBefore(parent, cur);
 | |
|             let oldLen = this.text.length;
 | |
|             this.readNode(cur);
 | |
|             let next = cur.nextSibling;
 | |
|             if (next == end)
 | |
|                 break;
 | |
|             let view = ContentView.get(cur), nextView = ContentView.get(next);
 | |
|             if (view && nextView ? view.breakAfter :
 | |
|                 (view ? view.breakAfter : isBlockElement(cur)) ||
 | |
|                     (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore) && this.text.length > oldLen))
 | |
|                 this.lineBreak();
 | |
|             cur = next;
 | |
|         }
 | |
|         this.findPointBefore(parent, end);
 | |
|         return this;
 | |
|     }
 | |
|     readTextNode(node) {
 | |
|         let text = node.nodeValue;
 | |
|         for (let point of this.points)
 | |
|             if (point.node == node)
 | |
|                 point.pos = this.text.length + Math.min(point.offset, text.length);
 | |
|         for (let off = 0, re = this.lineSeparator ? null : /\r\n?|\n/g;;) {
 | |
|             let nextBreak = -1, breakSize = 1, m;
 | |
|             if (this.lineSeparator) {
 | |
|                 nextBreak = text.indexOf(this.lineSeparator, off);
 | |
|                 breakSize = this.lineSeparator.length;
 | |
|             }
 | |
|             else if (m = re.exec(text)) {
 | |
|                 nextBreak = m.index;
 | |
|                 breakSize = m[0].length;
 | |
|             }
 | |
|             this.append(text.slice(off, nextBreak < 0 ? text.length : nextBreak));
 | |
|             if (nextBreak < 0)
 | |
|                 break;
 | |
|             this.lineBreak();
 | |
|             if (breakSize > 1)
 | |
|                 for (let point of this.points)
 | |
|                     if (point.node == node && point.pos > this.text.length)
 | |
|                         point.pos -= breakSize - 1;
 | |
|             off = nextBreak + breakSize;
 | |
|         }
 | |
|     }
 | |
|     readNode(node) {
 | |
|         if (node.cmIgnore)
 | |
|             return;
 | |
|         let view = ContentView.get(node);
 | |
|         let fromView = view && view.overrideDOMText;
 | |
|         if (fromView != null) {
 | |
|             this.findPointInside(node, fromView.length);
 | |
|             for (let i = fromView.iter(); !i.next().done;) {
 | |
|                 if (i.lineBreak)
 | |
|                     this.lineBreak();
 | |
|                 else
 | |
|                     this.append(i.value);
 | |
|             }
 | |
|         }
 | |
|         else if (node.nodeType == 3) {
 | |
|             this.readTextNode(node);
 | |
|         }
 | |
|         else if (node.nodeName == "BR") {
 | |
|             if (node.nextSibling)
 | |
|                 this.lineBreak();
 | |
|         }
 | |
|         else if (node.nodeType == 1) {
 | |
|             this.readRange(node.firstChild, null);
 | |
|         }
 | |
|     }
 | |
|     findPointBefore(node, next) {
 | |
|         for (let point of this.points)
 | |
|             if (point.node == node && node.childNodes[point.offset] == next)
 | |
|                 point.pos = this.text.length;
 | |
|     }
 | |
|     findPointInside(node, length) {
 | |
|         for (let point of this.points)
 | |
|             if (node.nodeType == 3 ? point.node == node : node.contains(point.node))
 | |
|                 point.pos = this.text.length + (isAtEnd(node, point.node, point.offset) ? length : 0);
 | |
|     }
 | |
| }
 | |
| function isAtEnd(parent, node, offset) {
 | |
|     for (;;) {
 | |
|         if (!node || offset < maxOffset(node))
 | |
|             return false;
 | |
|         if (node == parent)
 | |
|             return true;
 | |
|         offset = domIndex(node) + 1;
 | |
|         node = node.parentNode;
 | |
|     }
 | |
| }
 | |
| class DOMPoint {
 | |
|     constructor(node, offset) {
 | |
|         this.node = node;
 | |
|         this.offset = offset;
 | |
|         this.pos = -1;
 | |
|     }
 | |
| }
 | |
| 
 | |
| class DOMChange {
 | |
|     constructor(view, start, end, typeOver) {
 | |
|         this.typeOver = typeOver;
 | |
|         this.bounds = null;
 | |
|         this.text = "";
 | |
|         this.domChanged = start > -1;
 | |
|         let { impreciseHead: iHead, impreciseAnchor: iAnchor } = view.docView;
 | |
|         if (view.state.readOnly && start > -1) {
 | |
|             // Ignore changes when the editor is read-only
 | |
|             this.newSel = null;
 | |
|         }
 | |
|         else if (start > -1 && (this.bounds = view.docView.domBoundsAround(start, end, 0))) {
 | |
|             let selPoints = iHead || iAnchor ? [] : selectionPoints(view);
 | |
|             let reader = new DOMReader(selPoints, view.state);
 | |
|             reader.readRange(this.bounds.startDOM, this.bounds.endDOM);
 | |
|             this.text = reader.text;
 | |
|             this.newSel = selectionFromPoints(selPoints, this.bounds.from);
 | |
|         }
 | |
|         else {
 | |
|             let domSel = view.observer.selectionRange;
 | |
|             let head = iHead && iHead.node == domSel.focusNode && iHead.offset == domSel.focusOffset ||
 | |
|                 !contains(view.contentDOM, domSel.focusNode)
 | |
|                 ? view.state.selection.main.head
 | |
|                 : view.docView.posFromDOM(domSel.focusNode, domSel.focusOffset);
 | |
|             let anchor = iAnchor && iAnchor.node == domSel.anchorNode && iAnchor.offset == domSel.anchorOffset ||
 | |
|                 !contains(view.contentDOM, domSel.anchorNode)
 | |
|                 ? view.state.selection.main.anchor
 | |
|                 : view.docView.posFromDOM(domSel.anchorNode, domSel.anchorOffset);
 | |
|             // iOS will refuse to select the block gaps when doing
 | |
|             // select-all.
 | |
|             // Chrome will put the selection *inside* them, confusing
 | |
|             // posFromDOM
 | |
|             let vp = view.viewport;
 | |
|             if ((browser.ios || browser.chrome) && view.state.selection.main.empty && head != anchor &&
 | |
|                 (vp.from > 0 || vp.to < view.state.doc.length)) {
 | |
|                 let from = Math.min(head, anchor), to = Math.max(head, anchor);
 | |
|                 let offFrom = vp.from - from, offTo = vp.to - to;
 | |
|                 if ((offFrom == 0 || offFrom == 1 || from == 0) && (offTo == 0 || offTo == -1 || to == view.state.doc.length)) {
 | |
|                     head = 0;
 | |
|                     anchor = view.state.doc.length;
 | |
|                 }
 | |
|             }
 | |
|             this.newSel = EditorSelection.single(anchor, head);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function applyDOMChange(view, domChange) {
 | |
|     let change;
 | |
|     let { newSel } = domChange, sel = view.state.selection.main;
 | |
|     let lastKey = view.inputState.lastKeyTime > Date.now() - 100 ? view.inputState.lastKeyCode : -1;
 | |
|     if (domChange.bounds) {
 | |
|         let { from, to } = domChange.bounds;
 | |
|         let preferredPos = sel.from, preferredSide = null;
 | |
|         // Prefer anchoring to end when Backspace is pressed (or, on
 | |
|         // Android, when something was deleted)
 | |
|         if (lastKey === 8 || browser.android && domChange.text.length < to - from) {
 | |
|             preferredPos = sel.to;
 | |
|             preferredSide = "end";
 | |
|         }
 | |
|         let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), domChange.text, preferredPos - from, preferredSide);
 | |
|         if (diff) {
 | |
|             // Chrome inserts two newlines when pressing shift-enter at the
 | |
|             // end of a line. DomChange drops one of those.
 | |
|             if (browser.chrome && lastKey == 13 &&
 | |
|                 diff.toB == diff.from + 2 && domChange.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
 | |
|                 diff.toB--;
 | |
|             change = { from: from + diff.from, to: from + diff.toA,
 | |
|                 insert: Text.of(domChange.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
 | |
|         }
 | |
|     }
 | |
|     else if (newSel && (!view.hasFocus && view.state.facet(editable) || newSel.main.eq(sel))) {
 | |
|         newSel = null;
 | |
|     }
 | |
|     if (!change && !newSel)
 | |
|         return false;
 | |
|     if (!change && domChange.typeOver && !sel.empty && newSel && newSel.main.empty) {
 | |
|         // Heuristic to notice typing over a selected character
 | |
|         change = { from: sel.from, to: sel.to, insert: view.state.doc.slice(sel.from, sel.to) };
 | |
|     }
 | |
|     else if (change && change.from >= sel.from && change.to <= sel.to &&
 | |
|         (change.from != sel.from || change.to != sel.to) &&
 | |
|         (sel.to - sel.from) - (change.to - change.from) <= 4) {
 | |
|         // If the change is inside the selection and covers most of it,
 | |
|         // assume it is a selection replace (with identical characters at
 | |
|         // the start/end not included in the diff)
 | |
|         change = {
 | |
|             from: sel.from, to: sel.to,
 | |
|             insert: view.state.doc.slice(sel.from, change.from).append(change.insert).append(view.state.doc.slice(change.to, sel.to))
 | |
|         };
 | |
|     }
 | |
|     else if ((browser.mac || browser.android) && change && change.from == change.to && change.from == sel.head - 1 &&
 | |
|         /^\. ?$/.test(change.insert.toString()) && view.contentDOM.getAttribute("autocorrect") == "off") {
 | |
|         // Detect insert-period-on-double-space Mac and Android behavior,
 | |
|         // and transform it into a regular space insert.
 | |
|         if (newSel && change.insert.length == 2)
 | |
|             newSel = EditorSelection.single(newSel.main.anchor - 1, newSel.main.head - 1);
 | |
|         change = { from: sel.from, to: sel.to, insert: Text.of([" "]) };
 | |
|     }
 | |
|     else if (browser.chrome && change && change.from == change.to && change.from == sel.head &&
 | |
|         change.insert.toString() == "\n " && view.lineWrapping) {
 | |
|         // In Chrome, if you insert a space at the start of a wrapped
 | |
|         // line, it will actually insert a newline and a space, causing a
 | |
|         // bogus new line to be created in CodeMirror (#968)
 | |
|         if (newSel)
 | |
|             newSel = EditorSelection.single(newSel.main.anchor - 1, newSel.main.head - 1);
 | |
|         change = { from: sel.from, to: sel.to, insert: Text.of([" "]) };
 | |
|     }
 | |
|     if (change) {
 | |
|         return applyDOMChangeInner(view, change, newSel, lastKey);
 | |
|     }
 | |
|     else if (newSel && !newSel.main.eq(sel)) {
 | |
|         let scrollIntoView = false, userEvent = "select";
 | |
|         if (view.inputState.lastSelectionTime > Date.now() - 50) {
 | |
|             if (view.inputState.lastSelectionOrigin == "select")
 | |
|                 scrollIntoView = true;
 | |
|             userEvent = view.inputState.lastSelectionOrigin;
 | |
|         }
 | |
|         view.dispatch({ selection: newSel, scrollIntoView, userEvent });
 | |
|         return true;
 | |
|     }
 | |
|     else {
 | |
|         return false;
 | |
|     }
 | |
| }
 | |
| function applyDOMChangeInner(view, change, newSel, lastKey = -1) {
 | |
|     if (browser.ios && view.inputState.flushIOSKey(change))
 | |
|         return true;
 | |
|     let sel = view.state.selection.main;
 | |
|     // Android browsers don't fire reasonable key events for enter,
 | |
|     // backspace, or delete. So this detects changes that look like
 | |
|     // they're caused by those keys, and reinterprets them as key
 | |
|     // events. (Some of these keys are also handled by beforeinput
 | |
|     // events and the pendingAndroidKey mechanism, but that's not
 | |
|     // reliable in all situations.)
 | |
|     if (browser.android &&
 | |
|         ((change.to == sel.to &&
 | |
|             // GBoard will sometimes remove a space it just inserted
 | |
|             // after a completion when you press enter
 | |
|             (change.from == sel.from || change.from == sel.from - 1 && view.state.sliceDoc(change.from, sel.from) == " ") &&
 | |
|             change.insert.length == 1 && change.insert.lines == 2 &&
 | |
|             dispatchKey(view.contentDOM, "Enter", 13)) ||
 | |
|             ((change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 ||
 | |
|                 lastKey == 8 && change.insert.length < change.to - change.from && change.to > sel.head) &&
 | |
|                 dispatchKey(view.contentDOM, "Backspace", 8)) ||
 | |
|             (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
 | |
|                 dispatchKey(view.contentDOM, "Delete", 46))))
 | |
|         return true;
 | |
|     let text = change.insert.toString();
 | |
|     if (view.inputState.composing >= 0)
 | |
|         view.inputState.composing++;
 | |
|     let defaultTr;
 | |
|     let defaultInsert = () => defaultTr || (defaultTr = applyDefaultInsert(view, change, newSel));
 | |
|     if (!view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text, defaultInsert)))
 | |
|         view.dispatch(defaultInsert());
 | |
|     return true;
 | |
| }
 | |
| function applyDefaultInsert(view, change, newSel) {
 | |
|     let tr, startState = view.state, sel = startState.selection.main;
 | |
|     if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
 | |
|         (!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
 | |
|         view.inputState.composing < 0) {
 | |
|         let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
 | |
|         let after = sel.to > change.to ? startState.sliceDoc(change.to, sel.to) : "";
 | |
|         tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
 | |
|     }
 | |
|     else {
 | |
|         let changes = startState.changes(change);
 | |
|         let mainSel = newSel && newSel.main.to <= changes.newLength ? newSel.main : undefined;
 | |
|         // Try to apply a composition change to all cursors
 | |
|         if (startState.selection.ranges.length > 1 && view.inputState.composing >= 0 &&
 | |
|             change.to <= sel.to && change.to >= sel.to - 10) {
 | |
|             let replaced = view.state.sliceDoc(change.from, change.to);
 | |
|             let compositionRange, composition = newSel && findCompositionNode(view, newSel.main.head);
 | |
|             if (composition) {
 | |
|                 let dLen = change.insert.length - (change.to - change.from);
 | |
|                 compositionRange = { from: composition.from, to: composition.to - dLen };
 | |
|             }
 | |
|             else {
 | |
|                 compositionRange = view.state.doc.lineAt(sel.head);
 | |
|             }
 | |
|             let offset = sel.to - change.to, size = sel.to - sel.from;
 | |
|             tr = startState.changeByRange(range => {
 | |
|                 if (range.from == sel.from && range.to == sel.to)
 | |
|                     return { changes, range: mainSel || range.map(changes) };
 | |
|                 let to = range.to - offset, from = to - replaced.length;
 | |
|                 if (range.to - range.from != size || view.state.sliceDoc(from, to) != replaced ||
 | |
|                     // Unfortunately, there's no way to make multiple
 | |
|                     // changes in the same node work without aborting
 | |
|                     // composition, so cursors in the composition range are
 | |
|                     // ignored.
 | |
|                     range.to >= compositionRange.from && range.from <= compositionRange.to)
 | |
|                     return { range };
 | |
|                 let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
 | |
|                 return {
 | |
|                     changes: rangeChanges,
 | |
|                     range: !mainSel ? range.map(rangeChanges) :
 | |
|                         EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
 | |
|                 };
 | |
|             });
 | |
|         }
 | |
|         else {
 | |
|             tr = {
 | |
|                 changes,
 | |
|                 selection: mainSel && startState.selection.replaceRange(mainSel)
 | |
|             };
 | |
|         }
 | |
|     }
 | |
|     let userEvent = "input.type";
 | |
|     if (view.composing ||
 | |
|         view.inputState.compositionPendingChange && view.inputState.compositionEndedAt > Date.now() - 50) {
 | |
|         view.inputState.compositionPendingChange = false;
 | |
|         userEvent += ".compose";
 | |
|         if (view.inputState.compositionFirstChange) {
 | |
|             userEvent += ".start";
 | |
|             view.inputState.compositionFirstChange = false;
 | |
|         }
 | |
|     }
 | |
|     return startState.update(tr, { userEvent, scrollIntoView: true });
 | |
| }
 | |
| function findDiff(a, b, preferredPos, preferredSide) {
 | |
|     let minLen = Math.min(a.length, b.length);
 | |
|     let from = 0;
 | |
|     while (from < minLen && a.charCodeAt(from) == b.charCodeAt(from))
 | |
|         from++;
 | |
|     if (from == minLen && a.length == b.length)
 | |
|         return null;
 | |
|     let toA = a.length, toB = b.length;
 | |
|     while (toA > 0 && toB > 0 && a.charCodeAt(toA - 1) == b.charCodeAt(toB - 1)) {
 | |
|         toA--;
 | |
|         toB--;
 | |
|     }
 | |
|     if (preferredSide == "end") {
 | |
|         let adjust = Math.max(0, from - Math.min(toA, toB));
 | |
|         preferredPos -= toA + adjust - from;
 | |
|     }
 | |
|     if (toA < from && a.length < b.length) {
 | |
|         let move = preferredPos <= from && preferredPos >= toA ? from - preferredPos : 0;
 | |
|         from -= move;
 | |
|         toB = from + (toB - toA);
 | |
|         toA = from;
 | |
|     }
 | |
|     else if (toB < from) {
 | |
|         let move = preferredPos <= from && preferredPos >= toB ? from - preferredPos : 0;
 | |
|         from -= move;
 | |
|         toA = from + (toA - toB);
 | |
|         toB = from;
 | |
|     }
 | |
|     return { from, toA, toB };
 | |
| }
 | |
| function selectionPoints(view) {
 | |
|     let result = [];
 | |
|     if (view.root.activeElement != view.contentDOM)
 | |
|         return result;
 | |
|     let { anchorNode, anchorOffset, focusNode, focusOffset } = view.observer.selectionRange;
 | |
|     if (anchorNode) {
 | |
|         result.push(new DOMPoint(anchorNode, anchorOffset));
 | |
|         if (focusNode != anchorNode || focusOffset != anchorOffset)
 | |
|             result.push(new DOMPoint(focusNode, focusOffset));
 | |
|     }
 | |
|     return result;
 | |
| }
 | |
| function selectionFromPoints(points, base) {
 | |
|     if (points.length == 0)
 | |
|         return null;
 | |
|     let anchor = points[0].pos, head = points.length == 2 ? points[1].pos : anchor;
 | |
|     return anchor > -1 && head > -1 ? EditorSelection.single(anchor + base, head + base) : null;
 | |
| }
 | |
| 
 | |
| // This will also be where dragging info and such goes
 | |
| class InputState {
 | |
|     setSelectionOrigin(origin) {
 | |
|         this.lastSelectionOrigin = origin;
 | |
|         this.lastSelectionTime = Date.now();
 | |
|     }
 | |
|     constructor(view) {
 | |
|         this.view = view;
 | |
|         this.lastKeyCode = 0;
 | |
|         this.lastKeyTime = 0;
 | |
|         this.lastTouchTime = 0;
 | |
|         this.lastFocusTime = 0;
 | |
|         this.lastScrollTop = 0;
 | |
|         this.lastScrollLeft = 0;
 | |
|         // On iOS, some keys need to have their default behavior happen
 | |
|         // (after which we retroactively handle them and reset the DOM) to
 | |
|         // avoid messing up the virtual keyboard state.
 | |
|         this.pendingIOSKey = undefined;
 | |
|         /**
 | |
|         When enabled (>-1), tab presses are not given to key handlers,
 | |
|         leaving the browser's default behavior. If >0, the mode expires
 | |
|         at that timestamp, and any other keypress clears it.
 | |
|         Esc enables temporary tab focus mode for two seconds when not
 | |
|         otherwise handled.
 | |
|         */
 | |
|         this.tabFocusMode = -1;
 | |
|         this.lastSelectionOrigin = null;
 | |
|         this.lastSelectionTime = 0;
 | |
|         this.lastContextMenu = 0;
 | |
|         this.scrollHandlers = [];
 | |
|         this.handlers = Object.create(null);
 | |
|         // -1 means not in a composition. Otherwise, this counts the number
 | |
|         // of changes made during the composition. The count is used to
 | |
|         // avoid treating the start state of the composition, before any
 | |
|         // changes have been made, as part of the composition.
 | |
|         this.composing = -1;
 | |
|         // Tracks whether the next change should be marked as starting the
 | |
|         // composition (null means no composition, true means next is the
 | |
|         // first, false means first has already been marked for this
 | |
|         // composition)
 | |
|         this.compositionFirstChange = null;
 | |
|         // End time of the previous composition
 | |
|         this.compositionEndedAt = 0;
 | |
|         // Used in a kludge to detect when an Enter keypress should be
 | |
|         // considered part of the composition on Safari, which fires events
 | |
|         // in the wrong order
 | |
|         this.compositionPendingKey = false;
 | |
|         // Used to categorize changes as part of a composition, even when
 | |
|         // the mutation events fire shortly after the compositionend event
 | |
|         this.compositionPendingChange = false;
 | |
|         this.mouseSelection = null;
 | |
|         // When a drag from the editor is active, this points at the range
 | |
|         // being dragged.
 | |
|         this.draggedContent = null;
 | |
|         this.handleEvent = this.handleEvent.bind(this);
 | |
|         this.notifiedFocused = view.hasFocus;
 | |
|         // On Safari adding an input event handler somehow prevents an
 | |
|         // issue where the composition vanishes when you press enter.
 | |
|         if (browser.safari)
 | |
|             view.contentDOM.addEventListener("input", () => null);
 | |
|         if (browser.gecko)
 | |
|             firefoxCopyCutHack(view.contentDOM.ownerDocument);
 | |
|     }
 | |
|     handleEvent(event) {
 | |
|         if (!eventBelongsToEditor(this.view, event) || this.ignoreDuringComposition(event))
 | |
|             return;
 | |
|         if (event.type == "keydown" && this.keydown(event))
 | |
|             return;
 | |
|         this.runHandlers(event.type, event);
 | |
|     }
 | |
|     runHandlers(type, event) {
 | |
|         let handlers = this.handlers[type];
 | |
|         if (handlers) {
 | |
|             for (let observer of handlers.observers)
 | |
|                 observer(this.view, event);
 | |
|             for (let handler of handlers.handlers) {
 | |
|                 if (event.defaultPrevented)
 | |
|                     break;
 | |
|                 if (handler(this.view, event)) {
 | |
|                     event.preventDefault();
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     ensureHandlers(plugins) {
 | |
|         let handlers = computeHandlers(plugins), prev = this.handlers, dom = this.view.contentDOM;
 | |
|         for (let type in handlers)
 | |
|             if (type != "scroll") {
 | |
|                 let passive = !handlers[type].handlers.length;
 | |
|                 let exists = prev[type];
 | |
|                 if (exists && passive != !exists.handlers.length) {
 | |
|                     dom.removeEventListener(type, this.handleEvent);
 | |
|                     exists = null;
 | |
|                 }
 | |
|                 if (!exists)
 | |
|                     dom.addEventListener(type, this.handleEvent, { passive });
 | |
|             }
 | |
|         for (let type in prev)
 | |
|             if (type != "scroll" && !handlers[type])
 | |
|                 dom.removeEventListener(type, this.handleEvent);
 | |
|         this.handlers = handlers;
 | |
|     }
 | |
|     keydown(event) {
 | |
|         // Must always run, even if a custom handler handled the event
 | |
|         this.lastKeyCode = event.keyCode;
 | |
|         this.lastKeyTime = Date.now();
 | |
|         if (event.keyCode == 9 && this.tabFocusMode > -1 && (!this.tabFocusMode || Date.now() <= this.tabFocusMode))
 | |
|             return true;
 | |
|         if (this.tabFocusMode > 0 && event.keyCode != 27 && modifierCodes.indexOf(event.keyCode) < 0)
 | |
|             this.tabFocusMode = -1;
 | |
|         // Chrome for Android usually doesn't fire proper key events, but
 | |
|         // occasionally does, usually surrounded by a bunch of complicated
 | |
|         // composition changes. When an enter or backspace key event is
 | |
|         // seen, hold off on handling DOM events for a bit, and then
 | |
|         // dispatch it.
 | |
|         if (browser.android && browser.chrome && !event.synthetic &&
 | |
|             (event.keyCode == 13 || event.keyCode == 8)) {
 | |
|             this.view.observer.delayAndroidKey(event.key, event.keyCode);
 | |
|             return true;
 | |
|         }
 | |
|         // Preventing the default behavior of Enter on iOS makes the
 | |
|         // virtual keyboard get stuck in the wrong (lowercase)
 | |
|         // state. So we let it go through, and then, in
 | |
|         // applyDOMChange, notify key handlers of it and reset to
 | |
|         // the state they produce.
 | |
|         let pending;
 | |
|         if (browser.ios && !event.synthetic && !event.altKey && !event.metaKey &&
 | |
|             ((pending = PendingKeys.find(key => key.keyCode == event.keyCode)) && !event.ctrlKey ||
 | |
|                 EmacsyPendingKeys.indexOf(event.key) > -1 && event.ctrlKey && !event.shiftKey)) {
 | |
|             this.pendingIOSKey = pending || event;
 | |
|             setTimeout(() => this.flushIOSKey(), 250);
 | |
|             return true;
 | |
|         }
 | |
|         if (event.keyCode != 229)
 | |
|             this.view.observer.forceFlush();
 | |
|         return false;
 | |
|     }
 | |
|     flushIOSKey(change) {
 | |
|         let key = this.pendingIOSKey;
 | |
|         if (!key)
 | |
|             return false;
 | |
|         // This looks like an autocorrection before Enter
 | |
|         if (key.key == "Enter" && change && change.from < change.to && /^\S+$/.test(change.insert.toString()))
 | |
|             return false;
 | |
|         this.pendingIOSKey = undefined;
 | |
|         return dispatchKey(this.view.contentDOM, key.key, key.keyCode, key instanceof KeyboardEvent ? key : undefined);
 | |
|     }
 | |
|     ignoreDuringComposition(event) {
 | |
|         if (!/^key/.test(event.type))
 | |
|             return false;
 | |
|         if (this.composing > 0)
 | |
|             return true;
 | |
|         // See https://www.stum.de/2016/06/24/handling-ime-events-in-javascript/.
 | |
|         // On some input method editors (IMEs), the Enter key is used to
 | |
|         // confirm character selection. On Safari, when Enter is pressed,
 | |
|         // compositionend and keydown events are sometimes emitted in the
 | |
|         // wrong order. The key event should still be ignored, even when
 | |
|         // it happens after the compositionend event.
 | |
|         if (browser.safari && !browser.ios && this.compositionPendingKey && Date.now() - this.compositionEndedAt < 100) {
 | |
|             this.compositionPendingKey = false;
 | |
|             return true;
 | |
|         }
 | |
|         return false;
 | |
|     }
 | |
|     startMouseSelection(mouseSelection) {
 | |
|         if (this.mouseSelection)
 | |
|             this.mouseSelection.destroy();
 | |
|         this.mouseSelection = mouseSelection;
 | |
|     }
 | |
|     update(update) {
 | |
|         this.view.observer.update(update);
 | |
|         if (this.mouseSelection)
 | |
|             this.mouseSelection.update(update);
 | |
|         if (this.draggedContent && update.docChanged)
 | |
|             this.draggedContent = this.draggedContent.map(update.changes);
 | |
|         if (update.transactions.length)
 | |
|             this.lastKeyCode = this.lastSelectionTime = 0;
 | |
|     }
 | |
|     destroy() {
 | |
|         if (this.mouseSelection)
 | |
|             this.mouseSelection.destroy();
 | |
|     }
 | |
| }
 | |
| function bindHandler(plugin, handler) {
 | |
|     return (view, event) => {
 | |
|         try {
 | |
|             return handler.call(plugin, event, view);
 | |
|         }
 | |
|         catch (e) {
 | |
|             logException(view.state, e);
 | |
|         }
 | |
|     };
 | |
| }
 | |
| function computeHandlers(plugins) {
 | |
|     let result = Object.create(null);
 | |
|     function record(type) {
 | |
|         return result[type] || (result[type] = { observers: [], handlers: [] });
 | |
|     }
 | |
|     for (let plugin of plugins) {
 | |
|         let spec = plugin.spec;
 | |
|         if (spec && spec.domEventHandlers)
 | |
|             for (let type in spec.domEventHandlers) {
 | |
|                 let f = spec.domEventHandlers[type];
 | |
|                 if (f)
 | |
|                     record(type).handlers.push(bindHandler(plugin.value, f));
 | |
|             }
 | |
|         if (spec && spec.domEventObservers)
 | |
|             for (let type in spec.domEventObservers) {
 | |
|                 let f = spec.domEventObservers[type];
 | |
|                 if (f)
 | |
|                     record(type).observers.push(bindHandler(plugin.value, f));
 | |
|             }
 | |
|     }
 | |
|     for (let type in handlers)
 | |
|         record(type).handlers.push(handlers[type]);
 | |
|     for (let type in observers)
 | |
|         record(type).observers.push(observers[type]);
 | |
|     return result;
 | |
| }
 | |
| const PendingKeys = [
 | |
|     { key: "Backspace", keyCode: 8, inputType: "deleteContentBackward" },
 | |
|     { key: "Enter", keyCode: 13, inputType: "insertParagraph" },
 | |
|     { key: "Enter", keyCode: 13, inputType: "insertLineBreak" },
 | |
|     { key: "Delete", keyCode: 46, inputType: "deleteContentForward" }
 | |
| ];
 | |
| const EmacsyPendingKeys = "dthko";
 | |
| // Key codes for modifier keys
 | |
| const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
 | |
| const dragScrollMargin = 6;
 | |
| function dragScrollSpeed(dist) {
 | |
|     return Math.max(0, dist) * 0.7 + 8;
 | |
| }
 | |
| function dist(a, b) {
 | |
|     return Math.max(Math.abs(a.clientX - b.clientX), Math.abs(a.clientY - b.clientY));
 | |
| }
 | |
| class MouseSelection {
 | |
|     constructor(view, startEvent, style, mustSelect) {
 | |
|         this.view = view;
 | |
|         this.startEvent = startEvent;
 | |
|         this.style = style;
 | |
|         this.mustSelect = mustSelect;
 | |
|         this.scrollSpeed = { x: 0, y: 0 };
 | |
|         this.scrolling = -1;
 | |
|         this.lastEvent = startEvent;
 | |
|         this.scrollParents = scrollableParents(view.contentDOM);
 | |
|         this.atoms = view.state.facet(atomicRanges).map(f => f(view));
 | |
|         let doc = view.contentDOM.ownerDocument;
 | |
|         doc.addEventListener("mousemove", this.move = this.move.bind(this));
 | |
|         doc.addEventListener("mouseup", this.up = this.up.bind(this));
 | |
|         this.extend = startEvent.shiftKey;
 | |
|         this.multiple = view.state.facet(EditorState.allowMultipleSelections) && addsSelectionRange(view, startEvent);
 | |
|         this.dragging = isInPrimarySelection(view, startEvent) && getClickType(startEvent) == 1 ? null : false;
 | |
|     }
 | |
|     start(event) {
 | |
|         // When clicking outside of the selection, immediately apply the
 | |
|         // effect of starting the selection
 | |
|         if (this.dragging === false)
 | |
|             this.select(event);
 | |
|     }
 | |
|     move(event) {
 | |
|         if (event.buttons == 0)
 | |
|             return this.destroy();
 | |
|         if (this.dragging || this.dragging == null && dist(this.startEvent, event) < 10)
 | |
|             return;
 | |
|         this.select(this.lastEvent = event);
 | |
|         let sx = 0, sy = 0;
 | |
|         let left = 0, top = 0, right = this.view.win.innerWidth, bottom = this.view.win.innerHeight;
 | |
|         if (this.scrollParents.x)
 | |
|             ({ left, right } = this.scrollParents.x.getBoundingClientRect());
 | |
|         if (this.scrollParents.y)
 | |
|             ({ top, bottom } = this.scrollParents.y.getBoundingClientRect());
 | |
|         let margins = getScrollMargins(this.view);
 | |
|         if (event.clientX - margins.left <= left + dragScrollMargin)
 | |
|             sx = -dragScrollSpeed(left - event.clientX);
 | |
|         else if (event.clientX + margins.right >= right - dragScrollMargin)
 | |
|             sx = dragScrollSpeed(event.clientX - right);
 | |
|         if (event.clientY - margins.top <= top + dragScrollMargin)
 | |
|             sy = -dragScrollSpeed(top - event.clientY);
 | |
|         else if (event.clientY + margins.bottom >= bottom - dragScrollMargin)
 | |
|             sy = dragScrollSpeed(event.clientY - bottom);
 | |
|         this.setScrollSpeed(sx, sy);
 | |
|     }
 | |
|     up(event) {
 | |
|         if (this.dragging == null)
 | |
|             this.select(this.lastEvent);
 | |
|         if (!this.dragging)
 | |
|             event.preventDefault();
 | |
|         this.destroy();
 | |
|     }
 | |
|     destroy() {
 | |
|         this.setScrollSpeed(0, 0);
 | |
|         let doc = this.view.contentDOM.ownerDocument;
 | |
|         doc.removeEventListener("mousemove", this.move);
 | |
|         doc.removeEventListener("mouseup", this.up);
 | |
|         this.view.inputState.mouseSelection = this.view.inputState.draggedContent = null;
 | |
|     }
 | |
|     setScrollSpeed(sx, sy) {
 | |
|         this.scrollSpeed = { x: sx, y: sy };
 | |
|         if (sx || sy) {
 | |
|             if (this.scrolling < 0)
 | |
|                 this.scrolling = setInterval(() => this.scroll(), 50);
 | |
|         }
 | |
|         else if (this.scrolling > -1) {
 | |
|             clearInterval(this.scrolling);
 | |
|             this.scrolling = -1;
 | |
|         }
 | |
|     }
 | |
|     scroll() {
 | |
|         let { x, y } = this.scrollSpeed;
 | |
|         if (x && this.scrollParents.x) {
 | |
|             this.scrollParents.x.scrollLeft += x;
 | |
|             x = 0;
 | |
|         }
 | |
|         if (y && this.scrollParents.y) {
 | |
|             this.scrollParents.y.scrollTop += y;
 | |
|             y = 0;
 | |
|         }
 | |
|         if (x || y)
 | |
|             this.view.win.scrollBy(x, y);
 | |
|         if (this.dragging === false)
 | |
|             this.select(this.lastEvent);
 | |
|     }
 | |
|     skipAtoms(sel) {
 | |
|         let ranges = null;
 | |
|         for (let i = 0; i < sel.ranges.length; i++) {
 | |
|             let range = sel.ranges[i], updated = null;
 | |
|             if (range.empty) {
 | |
|                 let pos = skipAtomicRanges(this.atoms, range.from, 0);
 | |
|                 if (pos != range.from)
 | |
|                     updated = EditorSelection.cursor(pos, -1);
 | |
|             }
 | |
|             else {
 | |
|                 let from = skipAtomicRanges(this.atoms, range.from, -1);
 | |
|                 let to = skipAtomicRanges(this.atoms, range.to, 1);
 | |
|                 if (from != range.from || to != range.to)
 | |
|                     updated = EditorSelection.range(range.from == range.anchor ? from : to, range.from == range.head ? from : to);
 | |
|             }
 | |
|             if (updated) {
 | |
|                 if (!ranges)
 | |
|                     ranges = sel.ranges.slice();
 | |
|                 ranges[i] = updated;
 | |
|             }
 | |
|         }
 | |
|         return ranges ? EditorSelection.create(ranges, sel.mainIndex) : sel;
 | |
|     }
 | |
|     select(event) {
 | |
|         let { view } = this, selection = this.skipAtoms(this.style.get(event, this.extend, this.multiple));
 | |
|         if (this.mustSelect || !selection.eq(view.state.selection, this.dragging === false))
 | |
|             this.view.dispatch({
 | |
|                 selection,
 | |
|                 userEvent: "select.pointer"
 | |
|             });
 | |
|         this.mustSelect = false;
 | |
|     }
 | |
|     update(update) {
 | |
|         if (update.transactions.some(tr => tr.isUserEvent("input.type")))
 | |
|             this.destroy();
 | |
|         else if (this.style.update(update))
 | |
|             setTimeout(() => this.select(this.lastEvent), 20);
 | |
|     }
 | |
| }
 | |
| function addsSelectionRange(view, event) {
 | |
|     let facet = view.state.facet(clickAddsSelectionRange);
 | |
|     return facet.length ? facet[0](event) : browser.mac ? event.metaKey : event.ctrlKey;
 | |
| }
 | |
| function dragMovesSelection(view, event) {
 | |
|     let facet = view.state.facet(dragMovesSelection$1);
 | |
|     return facet.length ? facet[0](event) : browser.mac ? !event.altKey : !event.ctrlKey;
 | |
| }
 | |
| function isInPrimarySelection(view, event) {
 | |
|     let { main } = view.state.selection;
 | |
|     if (main.empty)
 | |
|         return false;
 | |
|     // On boundary clicks, check whether the coordinates are inside the
 | |
|     // selection's client rectangles
 | |
|     let sel = getSelection(view.root);
 | |
|     if (!sel || sel.rangeCount == 0)
 | |
|         return true;
 | |
|     let rects = sel.getRangeAt(0).getClientRects();
 | |
|     for (let i = 0; i < rects.length; i++) {
 | |
|         let rect = rects[i];
 | |
|         if (rect.left <= event.clientX && rect.right >= event.clientX &&
 | |
|             rect.top <= event.clientY && rect.bottom >= event.clientY)
 | |
|             return true;
 | |
|     }
 | |
|     return false;
 | |
| }
 | |
| function eventBelongsToEditor(view, event) {
 | |
|     if (!event.bubbles)
 | |
|         return true;
 | |
|     if (event.defaultPrevented)
 | |
|         return false;
 | |
|     for (let node = event.target, cView; node != view.contentDOM; node = node.parentNode)
 | |
|         if (!node || node.nodeType == 11 || ((cView = ContentView.get(node)) && cView.ignoreEvent(event)))
 | |
|             return false;
 | |
|     return true;
 | |
| }
 | |
| const handlers = /*@__PURE__*/Object.create(null);
 | |
| const observers = /*@__PURE__*/Object.create(null);
 | |
| // This is very crude, but unfortunately both these browsers _pretend_
 | |
| // that they have a clipboard API—all the objects and methods are
 | |
| // there, they just don't work, and they are hard to test.
 | |
| const brokenClipboardAPI = (browser.ie && browser.ie_version < 15) ||
 | |
|     (browser.ios && browser.webkit_version < 604);
 | |
| function capturePaste(view) {
 | |
|     let parent = view.dom.parentNode;
 | |
|     if (!parent)
 | |
|         return;
 | |
|     let target = parent.appendChild(document.createElement("textarea"));
 | |
|     target.style.cssText = "position: fixed; left: -10000px; top: 10px";
 | |
|     target.focus();
 | |
|     setTimeout(() => {
 | |
|         view.focus();
 | |
|         target.remove();
 | |
|         doPaste(view, target.value);
 | |
|     }, 50);
 | |
| }
 | |
| function doPaste(view, input) {
 | |
|     let { state } = view, changes, i = 1, text = state.toText(input);
 | |
|     let byLine = text.lines == state.selection.ranges.length;
 | |
|     let linewise = lastLinewiseCopy != null && state.selection.ranges.every(r => r.empty) && lastLinewiseCopy == text.toString();
 | |
|     if (linewise) {
 | |
|         let lastLine = -1;
 | |
|         changes = state.changeByRange(range => {
 | |
|             let line = state.doc.lineAt(range.from);
 | |
|             if (line.from == lastLine)
 | |
|                 return { range };
 | |
|             lastLine = line.from;
 | |
|             let insert = state.toText((byLine ? text.line(i++).text : input) + state.lineBreak);
 | |
|             return { changes: { from: line.from, insert },
 | |
|                 range: EditorSelection.cursor(range.from + insert.length) };
 | |
|         });
 | |
|     }
 | |
|     else if (byLine) {
 | |
|         changes = state.changeByRange(range => {
 | |
|             let line = text.line(i++);
 | |
|             return { changes: { from: range.from, to: range.to, insert: line.text },
 | |
|                 range: EditorSelection.cursor(range.from + line.length) };
 | |
|         });
 | |
|     }
 | |
|     else {
 | |
|         changes = state.replaceSelection(text);
 | |
|     }
 | |
|     view.dispatch(changes, {
 | |
|         userEvent: "input.paste",
 | |
|         scrollIntoView: true
 | |
|     });
 | |
| }
 | |
| observers.scroll = view => {
 | |
|     view.inputState.lastScrollTop = view.scrollDOM.scrollTop;
 | |
|     view.inputState.lastScrollLeft = view.scrollDOM.scrollLeft;
 | |
| };
 | |
| handlers.keydown = (view, event) => {
 | |
|     view.inputState.setSelectionOrigin("select");
 | |
|     if (event.keyCode == 27 && view.inputState.tabFocusMode != 0)
 | |
|         view.inputState.tabFocusMode = Date.now() + 2000;
 | |
|     return false;
 | |
| };
 | |
| observers.touchstart = (view, e) => {
 | |
|     view.inputState.lastTouchTime = Date.now();
 | |
|     view.inputState.setSelectionOrigin("select.pointer");
 | |
| };
 | |
| observers.touchmove = view => {
 | |
|     view.inputState.setSelectionOrigin("select.pointer");
 | |
| };
 | |
| handlers.mousedown = (view, event) => {
 | |
|     view.observer.flush();
 | |
|     if (view.inputState.lastTouchTime > Date.now() - 2000)
 | |
|         return false; // Ignore touch interaction
 | |
|     let style = null;
 | |
|     for (let makeStyle of view.state.facet(mouseSelectionStyle)) {
 | |
|         style = makeStyle(view, event);
 | |
|         if (style)
 | |
|             break;
 | |
|     }
 | |
|     if (!style && event.button == 0)
 | |
|         style = basicMouseSelection(view, event);
 | |
|     if (style) {
 | |
|         let mustFocus = !view.hasFocus;
 | |
|         view.inputState.startMouseSelection(new MouseSelection(view, event, style, mustFocus));
 | |
|         if (mustFocus)
 | |
|             view.observer.ignore(() => {
 | |
|                 focusPreventScroll(view.contentDOM);
 | |
|                 let active = view.root.activeElement;
 | |
|                 if (active && !active.contains(view.contentDOM))
 | |
|                     active.blur();
 | |
|             });
 | |
|         let mouseSel = view.inputState.mouseSelection;
 | |
|         if (mouseSel) {
 | |
|             mouseSel.start(event);
 | |
|             return mouseSel.dragging === false;
 | |
|         }
 | |
|     }
 | |
|     return false;
 | |
| };
 | |
| function rangeForClick(view, pos, bias, type) {
 | |
|     if (type == 1) { // Single click
 | |
|         return EditorSelection.cursor(pos, bias);
 | |
|     }
 | |
|     else if (type == 2) { // Double click
 | |
|         return groupAt(view.state, pos, bias);
 | |
|     }
 | |
|     else { // Triple click
 | |
|         let visual = LineView.find(view.docView, pos), line = view.state.doc.lineAt(visual ? visual.posAtEnd : pos);
 | |
|         let from = visual ? visual.posAtStart : line.from, to = visual ? visual.posAtEnd : line.to;
 | |
|         if (to < view.state.doc.length && to == line.to)
 | |
|             to++;
 | |
|         return EditorSelection.range(from, to);
 | |
|     }
 | |
| }
 | |
| let inside = (x, y, rect) => y >= rect.top && y <= rect.bottom && x >= rect.left && x <= rect.right;
 | |
| // Try to determine, for the given coordinates, associated with the
 | |
| // given position, whether they are related to the element before or
 | |
| // the element after the position.
 | |
| function findPositionSide(view, pos, x, y) {
 | |
|     let line = LineView.find(view.docView, pos);
 | |
|     if (!line)
 | |
|         return 1;
 | |
|     let off = pos - line.posAtStart;
 | |
|     // Line boundaries point into the line
 | |
|     if (off == 0)
 | |
|         return 1;
 | |
|     if (off == line.length)
 | |
|         return -1;
 | |
|     // Positions on top of an element point at that element
 | |
|     let before = line.coordsAt(off, -1);
 | |
|     if (before && inside(x, y, before))
 | |
|         return -1;
 | |
|     let after = line.coordsAt(off, 1);
 | |
|     if (after && inside(x, y, after))
 | |
|         return 1;
 | |
|     // This is probably a line wrap point. Pick before if the point is
 | |
|     // above its bottom.
 | |
|     return before && before.bottom >= y ? -1 : 1;
 | |
| }
 | |
| function queryPos(view, event) {
 | |
|     let pos = view.posAtCoords({ x: event.clientX, y: event.clientY }, false);
 | |
|     return { pos, bias: findPositionSide(view, pos, event.clientX, event.clientY) };
 | |
| }
 | |
| const BadMouseDetail = browser.ie && browser.ie_version <= 11;
 | |
| let lastMouseDown = null, lastMouseDownCount = 0, lastMouseDownTime = 0;
 | |
| function getClickType(event) {
 | |
|     if (!BadMouseDetail)
 | |
|         return event.detail;
 | |
|     let last = lastMouseDown, lastTime = lastMouseDownTime;
 | |
|     lastMouseDown = event;
 | |
|     lastMouseDownTime = Date.now();
 | |
|     return lastMouseDownCount = !last || (lastTime > Date.now() - 400 && Math.abs(last.clientX - event.clientX) < 2 &&
 | |
|         Math.abs(last.clientY - event.clientY) < 2) ? (lastMouseDownCount + 1) % 3 : 1;
 | |
| }
 | |
| function basicMouseSelection(view, event) {
 | |
|     let start = queryPos(view, event), type = getClickType(event);
 | |
|     let startSel = view.state.selection;
 | |
|     return {
 | |
|         update(update) {
 | |
|             if (update.docChanged) {
 | |
|                 start.pos = update.changes.mapPos(start.pos);
 | |
|                 startSel = startSel.map(update.changes);
 | |
|             }
 | |
|         },
 | |
|         get(event, extend, multiple) {
 | |
|             let cur = queryPos(view, event), removed;
 | |
|             let range = rangeForClick(view, cur.pos, cur.bias, type);
 | |
|             if (start.pos != cur.pos && !extend) {
 | |
|                 let startRange = rangeForClick(view, start.pos, start.bias, type);
 | |
|                 let from = Math.min(startRange.from, range.from), to = Math.max(startRange.to, range.to);
 | |
|                 range = from < range.from ? EditorSelection.range(from, to) : EditorSelection.range(to, from);
 | |
|             }
 | |
|             if (extend)
 | |
|                 return startSel.replaceRange(startSel.main.extend(range.from, range.to));
 | |
|             else if (multiple && type == 1 && startSel.ranges.length > 1 && (removed = removeRangeAround(startSel, cur.pos)))
 | |
|                 return removed;
 | |
|             else if (multiple)
 | |
|                 return startSel.addRange(range);
 | |
|             else
 | |
|                 return EditorSelection.create([range]);
 | |
|         }
 | |
|     };
 | |
| }
 | |
| function removeRangeAround(sel, pos) {
 | |
|     for (let i = 0; i < sel.ranges.length; i++) {
 | |
|         let { from, to } = sel.ranges[i];
 | |
|         if (from <= pos && to >= pos)
 | |
|             return EditorSelection.create(sel.ranges.slice(0, i).concat(sel.ranges.slice(i + 1)), sel.mainIndex == i ? 0 : sel.mainIndex - (sel.mainIndex > i ? 1 : 0));
 | |
|     }
 | |
|     return null;
 | |
| }
 | |
| handlers.dragstart = (view, event) => {
 | |
|     let { selection: { main: range } } = view.state;
 | |
|     if (event.target.draggable) {
 | |
|         let cView = view.docView.nearest(event.target);
 | |
|         if (cView && cView.isWidget) {
 | |
|             let from = cView.posAtStart, to = from + cView.length;
 | |
|             if (from >= range.to || to <= range.from)
 | |
|                 range = EditorSelection.range(from, to);
 | |
|         }
 | |
|     }
 | |
|     let { inputState } = view;
 | |
|     if (inputState.mouseSelection)
 | |
|         inputState.mouseSelection.dragging = true;
 | |
|     inputState.draggedContent = range;
 | |
|     if (event.dataTransfer) {
 | |
|         event.dataTransfer.setData("Text", view.state.sliceDoc(range.from, range.to));
 | |
|         event.dataTransfer.effectAllowed = "copyMove";
 | |
|     }
 | |
|     return false;
 | |
| };
 | |
| handlers.dragend = view => {
 | |
|     view.inputState.draggedContent = null;
 | |
|     return false;
 | |
| };
 | |
| function dropText(view, event, text, direct) {
 | |
|     if (!text)
 | |
|         return;
 | |
|     let dropPos = view.posAtCoords({ x: event.clientX, y: event.clientY }, false);
 | |
|     let { draggedContent } = view.inputState;
 | |
|     let del = direct && draggedContent && dragMovesSelection(view, event)
 | |
|         ? { from: draggedContent.from, to: draggedContent.to } : null;
 | |
|     let ins = { from: dropPos, insert: text };
 | |
|     let changes = view.state.changes(del ? [del, ins] : ins);
 | |
|     view.focus();
 | |
|     view.dispatch({
 | |
|         changes,
 | |
|         selection: { anchor: changes.mapPos(dropPos, -1), head: changes.mapPos(dropPos, 1) },
 | |
|         userEvent: del ? "move.drop" : "input.drop"
 | |
|     });
 | |
|     view.inputState.draggedContent = null;
 | |
| }
 | |
| handlers.drop = (view, event) => {
 | |
|     if (!event.dataTransfer)
 | |
|         return false;
 | |
|     if (view.state.readOnly)
 | |
|         return true;
 | |
|     let files = event.dataTransfer.files;
 | |
|     if (files && files.length) { // For a file drop, read the file's text.
 | |
|         let text = Array(files.length), read = 0;
 | |
|         let finishFile = () => {
 | |
|             if (++read == files.length)
 | |
|                 dropText(view, event, text.filter(s => s != null).join(view.state.lineBreak), false);
 | |
|         };
 | |
|         for (let i = 0; i < files.length; i++) {
 | |
|             let reader = new FileReader;
 | |
|             reader.onerror = finishFile;
 | |
|             reader.onload = () => {
 | |
|                 if (!/[\x00-\x08\x0e-\x1f]{2}/.test(reader.result))
 | |
|                     text[i] = reader.result;
 | |
|                 finishFile();
 | |
|             };
 | |
|             reader.readAsText(files[i]);
 | |
|         }
 | |
|         return true;
 | |
|     }
 | |
|     else {
 | |
|         let text = event.dataTransfer.getData("Text");
 | |
|         if (text) {
 | |
|             dropText(view, event, text, true);
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
|     return false;
 | |
| };
 | |
| handlers.paste = (view, event) => {
 | |
|     if (view.state.readOnly)
 | |
|         return true;
 | |
|     view.observer.flush();
 | |
|     let data = brokenClipboardAPI ? null : event.clipboardData;
 | |
|     if (data) {
 | |
|         doPaste(view, data.getData("text/plain") || data.getData("text/uri-list"));
 | |
|         return true;
 | |
|     }
 | |
|     else {
 | |
|         capturePaste(view);
 | |
|         return false;
 | |
|     }
 | |
| };
 | |
| function captureCopy(view, text) {
 | |
|     // The extra wrapper is somehow necessary on IE/Edge to prevent the
 | |
|     // content from being mangled when it is put onto the clipboard
 | |
|     let parent = view.dom.parentNode;
 | |
|     if (!parent)
 | |
|         return;
 | |
|     let target = parent.appendChild(document.createElement("textarea"));
 | |
|     target.style.cssText = "position: fixed; left: -10000px; top: 10px";
 | |
|     target.value = text;
 | |
|     target.focus();
 | |
|     target.selectionEnd = text.length;
 | |
|     target.selectionStart = 0;
 | |
|     setTimeout(() => {
 | |
|         target.remove();
 | |
|         view.focus();
 | |
|     }, 50);
 | |
| }
 | |
| function copiedRange(state) {
 | |
|     let content = [], ranges = [], linewise = false;
 | |
|     for (let range of state.selection.ranges)
 | |
|         if (!range.empty) {
 | |
|             content.push(state.sliceDoc(range.from, range.to));
 | |
|             ranges.push(range);
 | |
|         }
 | |
|     if (!content.length) {
 | |
|         // Nothing selected, do a line-wise copy
 | |
|         let upto = -1;
 | |
|         for (let { from } of state.selection.ranges) {
 | |
|             let line = state.doc.lineAt(from);
 | |
|             if (line.number > upto) {
 | |
|                 content.push(line.text);
 | |
|                 ranges.push({ from: line.from, to: Math.min(state.doc.length, line.to + 1) });
 | |
|             }
 | |
|             upto = line.number;
 | |
|         }
 | |
|         linewise = true;
 | |
|     }
 | |
|     return { text: content.join(state.lineBreak), ranges, linewise };
 | |
| }
 | |
| let lastLinewiseCopy = null;
 | |
| handlers.copy = handlers.cut = (view, event) => {
 | |
|     let { text, ranges, linewise } = copiedRange(view.state);
 | |
|     if (!text && !linewise)
 | |
|         return false;
 | |
|     lastLinewiseCopy = linewise ? text : null;
 | |
|     if (event.type == "cut" && !view.state.readOnly)
 | |
|         view.dispatch({
 | |
|             changes: ranges,
 | |
|             scrollIntoView: true,
 | |
|             userEvent: "delete.cut"
 | |
|         });
 | |
|     let data = brokenClipboardAPI ? null : event.clipboardData;
 | |
|     if (data) {
 | |
|         data.clearData();
 | |
|         data.setData("text/plain", text);
 | |
|         return true;
 | |
|     }
 | |
|     else {
 | |
|         captureCopy(view, text);
 | |
|         return false;
 | |
|     }
 | |
| };
 | |
| const isFocusChange = /*@__PURE__*/Annotation.define();
 | |
| function focusChangeTransaction(state, focus) {
 | |
|     let effects = [];
 | |
|     for (let getEffect of state.facet(focusChangeEffect)) {
 | |
|         let effect = getEffect(state, focus);
 | |
|         if (effect)
 | |
|             effects.push(effect);
 | |
|     }
 | |
|     return effects ? state.update({ effects, annotations: isFocusChange.of(true) }) : null;
 | |
| }
 | |
| function updateForFocusChange(view) {
 | |
|     setTimeout(() => {
 | |
|         let focus = view.hasFocus;
 | |
|         if (focus != view.inputState.notifiedFocused) {
 | |
|             let tr = focusChangeTransaction(view.state, focus);
 | |
|             if (tr)
 | |
|                 view.dispatch(tr);
 | |
|             else
 | |
|                 view.update([]);
 | |
|         }
 | |
|     }, 10);
 | |
| }
 | |
| observers.focus = view => {
 | |
|     view.inputState.lastFocusTime = Date.now();
 | |
|     // When focusing reset the scroll position, move it back to where it was
 | |
|     if (!view.scrollDOM.scrollTop && (view.inputState.lastScrollTop || view.inputState.lastScrollLeft)) {
 | |
|         view.scrollDOM.scrollTop = view.inputState.lastScrollTop;
 | |
|         view.scrollDOM.scrollLeft = view.inputState.lastScrollLeft;
 | |
|     }
 | |
|     updateForFocusChange(view);
 | |
| };
 | |
| observers.blur = view => {
 | |
|     view.observer.clearSelectionRange();
 | |
|     updateForFocusChange(view);
 | |
| };
 | |
| observers.compositionstart = observers.compositionupdate = view => {
 | |
|     if (view.observer.editContext)
 | |
|         return; // Composition handled by edit context
 | |
|     if (view.inputState.compositionFirstChange == null)
 | |
|         view.inputState.compositionFirstChange = true;
 | |
|     if (view.inputState.composing < 0) {
 | |
|         // FIXME possibly set a timeout to clear it again on Android
 | |
|         view.inputState.composing = 0;
 | |
|     }
 | |
| };
 | |
| observers.compositionend = view => {
 | |
|     if (view.observer.editContext)
 | |
|         return; // Composition handled by edit context
 | |
|     view.inputState.composing = -1;
 | |
|     view.inputState.compositionEndedAt = Date.now();
 | |
|     view.inputState.compositionPendingKey = true;
 | |
|     view.inputState.compositionPendingChange = view.observer.pendingRecords().length > 0;
 | |
|     view.inputState.compositionFirstChange = null;
 | |
|     if (browser.chrome && browser.android) {
 | |
|         // Delay flushing for a bit on Android because it'll often fire a
 | |
|         // bunch of contradictory changes in a row at end of compositon
 | |
|         view.observer.flushSoon();
 | |
|     }
 | |
|     else if (view.inputState.compositionPendingChange) {
 | |
|         // If we found pending records, schedule a flush.
 | |
|         Promise.resolve().then(() => view.observer.flush());
 | |
|     }
 | |
|     else {
 | |
|         // Otherwise, make sure that, if no changes come in soon, the
 | |
|         // composition view is cleared.
 | |
|         setTimeout(() => {
 | |
|             if (view.inputState.composing < 0 && view.docView.hasComposition)
 | |
|                 view.update([]);
 | |
|         }, 50);
 | |
|     }
 | |
| };
 | |
| observers.contextmenu = view => {
 | |
|     view.inputState.lastContextMenu = Date.now();
 | |
| };
 | |
| handlers.beforeinput = (view, event) => {
 | |
|     var _a, _b;
 | |
|     // In EditContext mode, we must handle insertReplacementText events
 | |
|     // directly, to make spell checking corrections work
 | |
|     if (event.inputType == "insertReplacementText" && view.observer.editContext) {
 | |
|         let text = (_a = event.dataTransfer) === null || _a === void 0 ? void 0 : _a.getData("text/plain"), ranges = event.getTargetRanges();
 | |
|         if (text && ranges.length) {
 | |
|             let r = ranges[0];
 | |
|             let from = view.posAtDOM(r.startContainer, r.startOffset), to = view.posAtDOM(r.endContainer, r.endOffset);
 | |
|             applyDOMChangeInner(view, { from, to, insert: view.state.toText(text) }, null);
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
|     // Because Chrome Android doesn't fire useful key events, use
 | |
|     // beforeinput to detect backspace (and possibly enter and delete,
 | |
|     // but those usually don't even seem to fire beforeinput events at
 | |
|     // the moment) and fake a key event for it.
 | |
|     //
 | |
|     // (preventDefault on beforeinput, though supported in the spec,
 | |
|     // seems to do nothing at all on Chrome).
 | |
|     let pending;
 | |
|     if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
 | |
|         view.observer.delayAndroidKey(pending.key, pending.keyCode);
 | |
|         if (pending.key == "Backspace" || pending.key == "Delete") {
 | |
|             let startViewHeight = ((_b = window.visualViewport) === null || _b === void 0 ? void 0 : _b.height) || 0;
 | |
|             setTimeout(() => {
 | |
|                 var _a;
 | |
|                 // Backspacing near uneditable nodes on Chrome Android sometimes
 | |
|                 // closes the virtual keyboard. This tries to crudely detect
 | |
|                 // that and refocus to get it back.
 | |
|                 if ((((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0) > startViewHeight + 10 && view.hasFocus) {
 | |
|                     view.contentDOM.blur();
 | |
|                     view.focus();
 | |
|                 }
 | |
|             }, 100);
 | |
|         }
 | |
|     }
 | |
|     if (browser.ios && event.inputType == "deleteContentForward") {
 | |
|         // For some reason, DOM changes (and beforeinput) happen _before_
 | |
|         // the key event for ctrl-d on iOS when using an external
 | |
|         // keyboard.
 | |
|         view.observer.flushSoon();
 | |
|     }
 | |
|     // Safari will occasionally forget to fire compositionend at the end of a dead-key composition
 | |
|     if (browser.safari && event.inputType == "insertText" && view.inputState.composing >= 0) {
 | |
|         setTimeout(() => observers.compositionend(view, event), 20);
 | |
|     }
 | |
|     return false;
 | |
| };
 | |
| const appliedFirefoxHack = /*@__PURE__*/new Set;
 | |
| // In Firefox, when cut/copy handlers are added to the document, that
 | |
| // somehow avoids a bug where those events aren't fired when the
 | |
| // selection is empty. See https://github.com/codemirror/dev/issues/1082
 | |
| // and https://bugzilla.mozilla.org/show_bug.cgi?id=995961
 | |
| function firefoxCopyCutHack(doc) {
 | |
|     if (!appliedFirefoxHack.has(doc)) {
 | |
|         appliedFirefoxHack.add(doc);
 | |
|         doc.addEventListener("copy", () => { });
 | |
|         doc.addEventListener("cut", () => { });
 | |
|     }
 | |
| }
 | |
| 
 | |
| const wrappingWhiteSpace = ["pre-wrap", "normal", "pre-line", "break-spaces"];
 | |
| // Used to track, during updateHeight, if any actual heights changed
 | |
| let heightChangeFlag = false;
 | |
| function clearHeightChangeFlag() { heightChangeFlag = false; }
 | |
| class HeightOracle {
 | |
|     constructor(lineWrapping) {
 | |
|         this.lineWrapping = lineWrapping;
 | |
|         this.doc = Text.empty;
 | |
|         this.heightSamples = {};
 | |
|         this.lineHeight = 14; // The height of an entire line (line-height)
 | |
|         this.charWidth = 7;
 | |
|         this.textHeight = 14; // The height of the actual font (font-size)
 | |
|         this.lineLength = 30;
 | |
|     }
 | |
|     heightForGap(from, to) {
 | |
|         let lines = this.doc.lineAt(to).number - this.doc.lineAt(from).number + 1;
 | |
|         if (this.lineWrapping)
 | |
|             lines += Math.max(0, Math.ceil(((to - from) - (lines * this.lineLength * 0.5)) / this.lineLength));
 | |
|         return this.lineHeight * lines;
 | |
|     }
 | |
|     heightForLine(length) {
 | |
|         if (!this.lineWrapping)
 | |
|             return this.lineHeight;
 | |
|         let lines = 1 + Math.max(0, Math.ceil((length - this.lineLength) / (this.lineLength - 5)));
 | |
|         return lines * this.lineHeight;
 | |
|     }
 | |
|     setDoc(doc) { this.doc = doc; return this; }
 | |
|     mustRefreshForWrapping(whiteSpace) {
 | |
|         return (wrappingWhiteSpace.indexOf(whiteSpace) > -1) != this.lineWrapping;
 | |
|     }
 | |
|     mustRefreshForHeights(lineHeights) {
 | |
|         let newHeight = false;
 | |
|         for (let i = 0; i < lineHeights.length; i++) {
 | |
|             let h = lineHeights[i];
 | |
|             if (h < 0) {
 | |
|                 i++;
 | |
|             }
 | |
|             else if (!this.heightSamples[Math.floor(h * 10)]) { // Round to .1 pixels
 | |
|                 newHeight = true;
 | |
|                 this.heightSamples[Math.floor(h * 10)] = true;
 | |
|             }
 | |
|         }
 | |
|         return newHeight;
 | |
|     }
 | |
|     refresh(whiteSpace, lineHeight, charWidth, textHeight, lineLength, knownHeights) {
 | |
|         let lineWrapping = wrappingWhiteSpace.indexOf(whiteSpace) > -1;
 | |
|         let changed = Math.round(lineHeight) != Math.round(this.lineHeight) || this.lineWrapping != lineWrapping;
 | |
|         this.lineWrapping = lineWrapping;
 | |
|         this.lineHeight = lineHeight;
 | |
|         this.charWidth = charWidth;
 | |
|         this.textHeight = textHeight;
 | |
|         this.lineLength = lineLength;
 | |
|         if (changed) {
 | |
|             this.heightSamples = {};
 | |
|             for (let i = 0; i < knownHeights.length; i++) {
 | |
|                 let h = knownHeights[i];
 | |
|                 if (h < 0)
 | |
|                     i++;
 | |
|                 else
 | |
|                     this.heightSamples[Math.floor(h * 10)] = true;
 | |
|             }
 | |
|         }
 | |
|         return changed;
 | |
|     }
 | |
| }
 | |
| // This object is used by `updateHeight` to make DOM measurements
 | |
| // arrive at the right nides. The `heights` array is a sequence of
 | |
| // block heights, starting from position `from`.
 | |
| class MeasuredHeights {
 | |
|     constructor(from, heights) {
 | |
|         this.from = from;
 | |
|         this.heights = heights;
 | |
|         this.index = 0;
 | |
|     }
 | |
|     get more() { return this.index < this.heights.length; }
 | |
| }
 | |
| /**
 | |
| Record used to represent information about a block-level element
 | |
| in the editor view.
 | |
| */
 | |
| class BlockInfo {
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     constructor(
 | |
|     /**
 | |
|     The start of the element in the document.
 | |
|     */
 | |
|     from, 
 | |
|     /**
 | |
|     The length of the element.
 | |
|     */
 | |
|     length, 
 | |
|     /**
 | |
|     The top position of the element (relative to the top of the
 | |
|     document).
 | |
|     */
 | |
|     top, 
 | |
|     /**
 | |
|     Its height.
 | |
|     */
 | |
|     height, 
 | |
|     /**
 | |
|     @internal Weird packed field that holds an array of children
 | |
|     for composite blocks, a decoration for block widgets, and a
 | |
|     number indicating the amount of widget-create line breaks for
 | |
|     text blocks.
 | |
|     */
 | |
|     _content) {
 | |
|         this.from = from;
 | |
|         this.length = length;
 | |
|         this.top = top;
 | |
|         this.height = height;
 | |
|         this._content = _content;
 | |
|     }
 | |
|     /**
 | |
|     The type of element this is. When querying lines, this may be
 | |
|     an array of all the blocks that make up the line.
 | |
|     */
 | |
|     get type() {
 | |
|         return typeof this._content == "number" ? BlockType.Text :
 | |
|             Array.isArray(this._content) ? this._content : this._content.type;
 | |
|     }
 | |
|     /**
 | |
|     The end of the element as a document position.
 | |
|     */
 | |
|     get to() { return this.from + this.length; }
 | |
|     /**
 | |
|     The bottom position of the element.
 | |
|     */
 | |
|     get bottom() { return this.top + this.height; }
 | |
|     /**
 | |
|     If this is a widget block, this will return the widget
 | |
|     associated with it.
 | |
|     */
 | |
|     get widget() {
 | |
|         return this._content instanceof PointDecoration ? this._content.widget : null;
 | |
|     }
 | |
|     /**
 | |
|     If this is a textblock, this holds the number of line breaks
 | |
|     that appear in widgets inside the block.
 | |
|     */
 | |
|     get widgetLineBreaks() {
 | |
|         return typeof this._content == "number" ? this._content : 0;
 | |
|     }
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     join(other) {
 | |
|         let content = (Array.isArray(this._content) ? this._content : [this])
 | |
|             .concat(Array.isArray(other._content) ? other._content : [other]);
 | |
|         return new BlockInfo(this.from, this.length + other.length, this.top, this.height + other.height, content);
 | |
|     }
 | |
| }
 | |
| var QueryType = /*@__PURE__*/(function (QueryType) {
 | |
|     QueryType[QueryType["ByPos"] = 0] = "ByPos";
 | |
|     QueryType[QueryType["ByHeight"] = 1] = "ByHeight";
 | |
|     QueryType[QueryType["ByPosNoHeight"] = 2] = "ByPosNoHeight";
 | |
| return QueryType})(QueryType || (QueryType = {}));
 | |
| const Epsilon = 1e-3;
 | |
| class HeightMap {
 | |
|     constructor(length, // The number of characters covered
 | |
|     height, // Height of this part of the document
 | |
|     flags = 2 /* Flag.Outdated */) {
 | |
|         this.length = length;
 | |
|         this.height = height;
 | |
|         this.flags = flags;
 | |
|     }
 | |
|     get outdated() { return (this.flags & 2 /* Flag.Outdated */) > 0; }
 | |
|     set outdated(value) { this.flags = (value ? 2 /* Flag.Outdated */ : 0) | (this.flags & ~2 /* Flag.Outdated */); }
 | |
|     setHeight(height) {
 | |
|         if (this.height != height) {
 | |
|             if (Math.abs(this.height - height) > Epsilon)
 | |
|                 heightChangeFlag = true;
 | |
|             this.height = height;
 | |
|         }
 | |
|     }
 | |
|     // Base case is to replace a leaf node, which simply builds a tree
 | |
|     // from the new nodes and returns that (HeightMapBranch and
 | |
|     // HeightMapGap override this to actually use from/to)
 | |
|     replace(_from, _to, nodes) {
 | |
|         return HeightMap.of(nodes);
 | |
|     }
 | |
|     // Again, these are base cases, and are overridden for branch and gap nodes.
 | |
|     decomposeLeft(_to, result) { result.push(this); }
 | |
|     decomposeRight(_from, result) { result.push(this); }
 | |
|     applyChanges(decorations, oldDoc, oracle, changes) {
 | |
|         let me = this, doc = oracle.doc;
 | |
|         for (let i = changes.length - 1; i >= 0; i--) {
 | |
|             let { fromA, toA, fromB, toB } = changes[i];
 | |
|             let start = me.lineAt(fromA, QueryType.ByPosNoHeight, oracle.setDoc(oldDoc), 0, 0);
 | |
|             let end = start.to >= toA ? start : me.lineAt(toA, QueryType.ByPosNoHeight, oracle, 0, 0);
 | |
|             toB += end.to - toA;
 | |
|             toA = end.to;
 | |
|             while (i > 0 && start.from <= changes[i - 1].toA) {
 | |
|                 fromA = changes[i - 1].fromA;
 | |
|                 fromB = changes[i - 1].fromB;
 | |
|                 i--;
 | |
|                 if (fromA < start.from)
 | |
|                     start = me.lineAt(fromA, QueryType.ByPosNoHeight, oracle, 0, 0);
 | |
|             }
 | |
|             fromB += start.from - fromA;
 | |
|             fromA = start.from;
 | |
|             let nodes = NodeBuilder.build(oracle.setDoc(doc), decorations, fromB, toB);
 | |
|             me = replace(me, me.replace(fromA, toA, nodes));
 | |
|         }
 | |
|         return me.updateHeight(oracle, 0);
 | |
|     }
 | |
|     static empty() { return new HeightMapText(0, 0); }
 | |
|     // nodes uses null values to indicate the position of line breaks.
 | |
|     // There are never line breaks at the start or end of the array, or
 | |
|     // two line breaks next to each other, and the array isn't allowed
 | |
|     // to be empty (same restrictions as return value from the builder).
 | |
|     static of(nodes) {
 | |
|         if (nodes.length == 1)
 | |
|             return nodes[0];
 | |
|         let i = 0, j = nodes.length, before = 0, after = 0;
 | |
|         for (;;) {
 | |
|             if (i == j) {
 | |
|                 if (before > after * 2) {
 | |
|                     let split = nodes[i - 1];
 | |
|                     if (split.break)
 | |
|                         nodes.splice(--i, 1, split.left, null, split.right);
 | |
|                     else
 | |
|                         nodes.splice(--i, 1, split.left, split.right);
 | |
|                     j += 1 + split.break;
 | |
|                     before -= split.size;
 | |
|                 }
 | |
|                 else if (after > before * 2) {
 | |
|                     let split = nodes[j];
 | |
|                     if (split.break)
 | |
|                         nodes.splice(j, 1, split.left, null, split.right);
 | |
|                     else
 | |
|                         nodes.splice(j, 1, split.left, split.right);
 | |
|                     j += 2 + split.break;
 | |
|                     after -= split.size;
 | |
|                 }
 | |
|                 else {
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|             else if (before < after) {
 | |
|                 let next = nodes[i++];
 | |
|                 if (next)
 | |
|                     before += next.size;
 | |
|             }
 | |
|             else {
 | |
|                 let next = nodes[--j];
 | |
|                 if (next)
 | |
|                     after += next.size;
 | |
|             }
 | |
|         }
 | |
|         let brk = 0;
 | |
|         if (nodes[i - 1] == null) {
 | |
|             brk = 1;
 | |
|             i--;
 | |
|         }
 | |
|         else if (nodes[i] == null) {
 | |
|             brk = 1;
 | |
|             j++;
 | |
|         }
 | |
|         return new HeightMapBranch(HeightMap.of(nodes.slice(0, i)), brk, HeightMap.of(nodes.slice(j)));
 | |
|     }
 | |
| }
 | |
| function replace(old, val) {
 | |
|     if (old == val)
 | |
|         return old;
 | |
|     if (old.constructor != val.constructor)
 | |
|         heightChangeFlag = true;
 | |
|     return val;
 | |
| }
 | |
| HeightMap.prototype.size = 1;
 | |
| class HeightMapBlock extends HeightMap {
 | |
|     constructor(length, height, deco) {
 | |
|         super(length, height);
 | |
|         this.deco = deco;
 | |
|     }
 | |
|     blockAt(_height, _oracle, top, offset) {
 | |
|         return new BlockInfo(offset, this.length, top, this.height, this.deco || 0);
 | |
|     }
 | |
|     lineAt(_value, _type, oracle, top, offset) {
 | |
|         return this.blockAt(0, oracle, top, offset);
 | |
|     }
 | |
|     forEachLine(from, to, oracle, top, offset, f) {
 | |
|         if (from <= offset + this.length && to >= offset)
 | |
|             f(this.blockAt(0, oracle, top, offset));
 | |
|     }
 | |
|     updateHeight(oracle, offset = 0, _force = false, measured) {
 | |
|         if (measured && measured.from <= offset && measured.more)
 | |
|             this.setHeight(measured.heights[measured.index++]);
 | |
|         this.outdated = false;
 | |
|         return this;
 | |
|     }
 | |
|     toString() { return `block(${this.length})`; }
 | |
| }
 | |
| class HeightMapText extends HeightMapBlock {
 | |
|     constructor(length, height) {
 | |
|         super(length, height, null);
 | |
|         this.collapsed = 0; // Amount of collapsed content in the line
 | |
|         this.widgetHeight = 0; // Maximum inline widget height
 | |
|         this.breaks = 0; // Number of widget-introduced line breaks on the line
 | |
|     }
 | |
|     blockAt(_height, _oracle, top, offset) {
 | |
|         return new BlockInfo(offset, this.length, top, this.height, this.breaks);
 | |
|     }
 | |
|     replace(_from, _to, nodes) {
 | |
|         let node = nodes[0];
 | |
|         if (nodes.length == 1 && (node instanceof HeightMapText || node instanceof HeightMapGap && (node.flags & 4 /* Flag.SingleLine */)) &&
 | |
|             Math.abs(this.length - node.length) < 10) {
 | |
|             if (node instanceof HeightMapGap)
 | |
|                 node = new HeightMapText(node.length, this.height);
 | |
|             else
 | |
|                 node.height = this.height;
 | |
|             if (!this.outdated)
 | |
|                 node.outdated = false;
 | |
|             return node;
 | |
|         }
 | |
|         else {
 | |
|             return HeightMap.of(nodes);
 | |
|         }
 | |
|     }
 | |
|     updateHeight(oracle, offset = 0, force = false, measured) {
 | |
|         if (measured && measured.from <= offset && measured.more)
 | |
|             this.setHeight(measured.heights[measured.index++]);
 | |
|         else if (force || this.outdated)
 | |
|             this.setHeight(Math.max(this.widgetHeight, oracle.heightForLine(this.length - this.collapsed)) +
 | |
|                 this.breaks * oracle.lineHeight);
 | |
|         this.outdated = false;
 | |
|         return this;
 | |
|     }
 | |
|     toString() {
 | |
|         return `line(${this.length}${this.collapsed ? -this.collapsed : ""}${this.widgetHeight ? ":" + this.widgetHeight : ""})`;
 | |
|     }
 | |
| }
 | |
| class HeightMapGap extends HeightMap {
 | |
|     constructor(length) { super(length, 0); }
 | |
|     heightMetrics(oracle, offset) {
 | |
|         let firstLine = oracle.doc.lineAt(offset).number, lastLine = oracle.doc.lineAt(offset + this.length).number;
 | |
|         let lines = lastLine - firstLine + 1;
 | |
|         let perLine, perChar = 0;
 | |
|         if (oracle.lineWrapping) {
 | |
|             let totalPerLine = Math.min(this.height, oracle.lineHeight * lines);
 | |
|             perLine = totalPerLine / lines;
 | |
|             if (this.length > lines + 1)
 | |
|                 perChar = (this.height - totalPerLine) / (this.length - lines - 1);
 | |
|         }
 | |
|         else {
 | |
|             perLine = this.height / lines;
 | |
|         }
 | |
|         return { firstLine, lastLine, perLine, perChar };
 | |
|     }
 | |
|     blockAt(height, oracle, top, offset) {
 | |
|         let { firstLine, lastLine, perLine, perChar } = this.heightMetrics(oracle, offset);
 | |
|         if (oracle.lineWrapping) {
 | |
|             let guess = offset + (height < oracle.lineHeight ? 0
 | |
|                 : Math.round(Math.max(0, Math.min(1, (height - top) / this.height)) * this.length));
 | |
|             let line = oracle.doc.lineAt(guess), lineHeight = perLine + line.length * perChar;
 | |
|             let lineTop = Math.max(top, height - lineHeight / 2);
 | |
|             return new BlockInfo(line.from, line.length, lineTop, lineHeight, 0);
 | |
|         }
 | |
|         else {
 | |
|             let line = Math.max(0, Math.min(lastLine - firstLine, Math.floor((height - top) / perLine)));
 | |
|             let { from, length } = oracle.doc.line(firstLine + line);
 | |
|             return new BlockInfo(from, length, top + perLine * line, perLine, 0);
 | |
|         }
 | |
|     }
 | |
|     lineAt(value, type, oracle, top, offset) {
 | |
|         if (type == QueryType.ByHeight)
 | |
|             return this.blockAt(value, oracle, top, offset);
 | |
|         if (type == QueryType.ByPosNoHeight) {
 | |
|             let { from, to } = oracle.doc.lineAt(value);
 | |
|             return new BlockInfo(from, to - from, 0, 0, 0);
 | |
|         }
 | |
|         let { firstLine, perLine, perChar } = this.heightMetrics(oracle, offset);
 | |
|         let line = oracle.doc.lineAt(value), lineHeight = perLine + line.length * perChar;
 | |
|         let linesAbove = line.number - firstLine;
 | |
|         let lineTop = top + perLine * linesAbove + perChar * (line.from - offset - linesAbove);
 | |
|         return new BlockInfo(line.from, line.length, Math.max(top, Math.min(lineTop, top + this.height - lineHeight)), lineHeight, 0);
 | |
|     }
 | |
|     forEachLine(from, to, oracle, top, offset, f) {
 | |
|         from = Math.max(from, offset);
 | |
|         to = Math.min(to, offset + this.length);
 | |
|         let { firstLine, perLine, perChar } = this.heightMetrics(oracle, offset);
 | |
|         for (let pos = from, lineTop = top; pos <= to;) {
 | |
|             let line = oracle.doc.lineAt(pos);
 | |
|             if (pos == from) {
 | |
|                 let linesAbove = line.number - firstLine;
 | |
|                 lineTop += perLine * linesAbove + perChar * (from - offset - linesAbove);
 | |
|             }
 | |
|             let lineHeight = perLine + perChar * line.length;
 | |
|             f(new BlockInfo(line.from, line.length, lineTop, lineHeight, 0));
 | |
|             lineTop += lineHeight;
 | |
|             pos = line.to + 1;
 | |
|         }
 | |
|     }
 | |
|     replace(from, to, nodes) {
 | |
|         let after = this.length - to;
 | |
|         if (after > 0) {
 | |
|             let last = nodes[nodes.length - 1];
 | |
|             if (last instanceof HeightMapGap)
 | |
|                 nodes[nodes.length - 1] = new HeightMapGap(last.length + after);
 | |
|             else
 | |
|                 nodes.push(null, new HeightMapGap(after - 1));
 | |
|         }
 | |
|         if (from > 0) {
 | |
|             let first = nodes[0];
 | |
|             if (first instanceof HeightMapGap)
 | |
|                 nodes[0] = new HeightMapGap(from + first.length);
 | |
|             else
 | |
|                 nodes.unshift(new HeightMapGap(from - 1), null);
 | |
|         }
 | |
|         return HeightMap.of(nodes);
 | |
|     }
 | |
|     decomposeLeft(to, result) {
 | |
|         result.push(new HeightMapGap(to - 1), null);
 | |
|     }
 | |
|     decomposeRight(from, result) {
 | |
|         result.push(null, new HeightMapGap(this.length - from - 1));
 | |
|     }
 | |
|     updateHeight(oracle, offset = 0, force = false, measured) {
 | |
|         let end = offset + this.length;
 | |
|         if (measured && measured.from <= offset + this.length && measured.more) {
 | |
|             // Fill in part of this gap with measured lines. We know there
 | |
|             // can't be widgets or collapsed ranges in those lines, because
 | |
|             // they would already have been added to the heightmap (gaps
 | |
|             // only contain plain text).
 | |
|             let nodes = [], pos = Math.max(offset, measured.from), singleHeight = -1;
 | |
|             if (measured.from > offset)
 | |
|                 nodes.push(new HeightMapGap(measured.from - offset - 1).updateHeight(oracle, offset));
 | |
|             while (pos <= end && measured.more) {
 | |
|                 let len = oracle.doc.lineAt(pos).length;
 | |
|                 if (nodes.length)
 | |
|                     nodes.push(null);
 | |
|                 let height = measured.heights[measured.index++];
 | |
|                 if (singleHeight == -1)
 | |
|                     singleHeight = height;
 | |
|                 else if (Math.abs(height - singleHeight) >= Epsilon)
 | |
|                     singleHeight = -2;
 | |
|                 let line = new HeightMapText(len, height);
 | |
|                 line.outdated = false;
 | |
|                 nodes.push(line);
 | |
|                 pos += len + 1;
 | |
|             }
 | |
|             if (pos <= end)
 | |
|                 nodes.push(null, new HeightMapGap(end - pos).updateHeight(oracle, pos));
 | |
|             let result = HeightMap.of(nodes);
 | |
|             if (singleHeight < 0 || Math.abs(result.height - this.height) >= Epsilon ||
 | |
|                 Math.abs(singleHeight - this.heightMetrics(oracle, offset).perLine) >= Epsilon)
 | |
|                 heightChangeFlag = true;
 | |
|             return replace(this, result);
 | |
|         }
 | |
|         else if (force || this.outdated) {
 | |
|             this.setHeight(oracle.heightForGap(offset, offset + this.length));
 | |
|             this.outdated = false;
 | |
|         }
 | |
|         return this;
 | |
|     }
 | |
|     toString() { return `gap(${this.length})`; }
 | |
| }
 | |
| class HeightMapBranch extends HeightMap {
 | |
|     constructor(left, brk, right) {
 | |
|         super(left.length + brk + right.length, left.height + right.height, brk | (left.outdated || right.outdated ? 2 /* Flag.Outdated */ : 0));
 | |
|         this.left = left;
 | |
|         this.right = right;
 | |
|         this.size = left.size + right.size;
 | |
|     }
 | |
|     get break() { return this.flags & 1 /* Flag.Break */; }
 | |
|     blockAt(height, oracle, top, offset) {
 | |
|         let mid = top + this.left.height;
 | |
|         return height < mid ? this.left.blockAt(height, oracle, top, offset)
 | |
|             : this.right.blockAt(height, oracle, mid, offset + this.left.length + this.break);
 | |
|     }
 | |
|     lineAt(value, type, oracle, top, offset) {
 | |
|         let rightTop = top + this.left.height, rightOffset = offset + this.left.length + this.break;
 | |
|         let left = type == QueryType.ByHeight ? value < rightTop : value < rightOffset;
 | |
|         let base = left ? this.left.lineAt(value, type, oracle, top, offset)
 | |
|             : this.right.lineAt(value, type, oracle, rightTop, rightOffset);
 | |
|         if (this.break || (left ? base.to < rightOffset : base.from > rightOffset))
 | |
|             return base;
 | |
|         let subQuery = type == QueryType.ByPosNoHeight ? QueryType.ByPosNoHeight : QueryType.ByPos;
 | |
|         if (left)
 | |
|             return base.join(this.right.lineAt(rightOffset, subQuery, oracle, rightTop, rightOffset));
 | |
|         else
 | |
|             return this.left.lineAt(rightOffset, subQuery, oracle, top, offset).join(base);
 | |
|     }
 | |
|     forEachLine(from, to, oracle, top, offset, f) {
 | |
|         let rightTop = top + this.left.height, rightOffset = offset + this.left.length + this.break;
 | |
|         if (this.break) {
 | |
|             if (from < rightOffset)
 | |
|                 this.left.forEachLine(from, to, oracle, top, offset, f);
 | |
|             if (to >= rightOffset)
 | |
|                 this.right.forEachLine(from, to, oracle, rightTop, rightOffset, f);
 | |
|         }
 | |
|         else {
 | |
|             let mid = this.lineAt(rightOffset, QueryType.ByPos, oracle, top, offset);
 | |
|             if (from < mid.from)
 | |
|                 this.left.forEachLine(from, mid.from - 1, oracle, top, offset, f);
 | |
|             if (mid.to >= from && mid.from <= to)
 | |
|                 f(mid);
 | |
|             if (to > mid.to)
 | |
|                 this.right.forEachLine(mid.to + 1, to, oracle, rightTop, rightOffset, f);
 | |
|         }
 | |
|     }
 | |
|     replace(from, to, nodes) {
 | |
|         let rightStart = this.left.length + this.break;
 | |
|         if (to < rightStart)
 | |
|             return this.balanced(this.left.replace(from, to, nodes), this.right);
 | |
|         if (from > this.left.length)
 | |
|             return this.balanced(this.left, this.right.replace(from - rightStart, to - rightStart, nodes));
 | |
|         let result = [];
 | |
|         if (from > 0)
 | |
|             this.decomposeLeft(from, result);
 | |
|         let left = result.length;
 | |
|         for (let node of nodes)
 | |
|             result.push(node);
 | |
|         if (from > 0)
 | |
|             mergeGaps(result, left - 1);
 | |
|         if (to < this.length) {
 | |
|             let right = result.length;
 | |
|             this.decomposeRight(to, result);
 | |
|             mergeGaps(result, right);
 | |
|         }
 | |
|         return HeightMap.of(result);
 | |
|     }
 | |
|     decomposeLeft(to, result) {
 | |
|         let left = this.left.length;
 | |
|         if (to <= left)
 | |
|             return this.left.decomposeLeft(to, result);
 | |
|         result.push(this.left);
 | |
|         if (this.break) {
 | |
|             left++;
 | |
|             if (to >= left)
 | |
|                 result.push(null);
 | |
|         }
 | |
|         if (to > left)
 | |
|             this.right.decomposeLeft(to - left, result);
 | |
|     }
 | |
|     decomposeRight(from, result) {
 | |
|         let left = this.left.length, right = left + this.break;
 | |
|         if (from >= right)
 | |
|             return this.right.decomposeRight(from - right, result);
 | |
|         if (from < left)
 | |
|             this.left.decomposeRight(from, result);
 | |
|         if (this.break && from < right)
 | |
|             result.push(null);
 | |
|         result.push(this.right);
 | |
|     }
 | |
|     balanced(left, right) {
 | |
|         if (left.size > 2 * right.size || right.size > 2 * left.size)
 | |
|             return HeightMap.of(this.break ? [left, null, right] : [left, right]);
 | |
|         this.left = replace(this.left, left);
 | |
|         this.right = replace(this.right, right);
 | |
|         this.setHeight(left.height + right.height);
 | |
|         this.outdated = left.outdated || right.outdated;
 | |
|         this.size = left.size + right.size;
 | |
|         this.length = left.length + this.break + right.length;
 | |
|         return this;
 | |
|     }
 | |
|     updateHeight(oracle, offset = 0, force = false, measured) {
 | |
|         let { left, right } = this, rightStart = offset + left.length + this.break, rebalance = null;
 | |
|         if (measured && measured.from <= offset + left.length && measured.more)
 | |
|             rebalance = left = left.updateHeight(oracle, offset, force, measured);
 | |
|         else
 | |
|             left.updateHeight(oracle, offset, force);
 | |
|         if (measured && measured.from <= rightStart + right.length && measured.more)
 | |
|             rebalance = right = right.updateHeight(oracle, rightStart, force, measured);
 | |
|         else
 | |
|             right.updateHeight(oracle, rightStart, force);
 | |
|         if (rebalance)
 | |
|             return this.balanced(left, right);
 | |
|         this.height = this.left.height + this.right.height;
 | |
|         this.outdated = false;
 | |
|         return this;
 | |
|     }
 | |
|     toString() { return this.left + (this.break ? " " : "-") + this.right; }
 | |
| }
 | |
| function mergeGaps(nodes, around) {
 | |
|     let before, after;
 | |
|     if (nodes[around] == null &&
 | |
|         (before = nodes[around - 1]) instanceof HeightMapGap &&
 | |
|         (after = nodes[around + 1]) instanceof HeightMapGap)
 | |
|         nodes.splice(around - 1, 3, new HeightMapGap(before.length + 1 + after.length));
 | |
| }
 | |
| const relevantWidgetHeight = 5;
 | |
| class NodeBuilder {
 | |
|     constructor(pos, oracle) {
 | |
|         this.pos = pos;
 | |
|         this.oracle = oracle;
 | |
|         this.nodes = [];
 | |
|         this.lineStart = -1;
 | |
|         this.lineEnd = -1;
 | |
|         this.covering = null;
 | |
|         this.writtenTo = pos;
 | |
|     }
 | |
|     get isCovered() {
 | |
|         return this.covering && this.nodes[this.nodes.length - 1] == this.covering;
 | |
|     }
 | |
|     span(_from, to) {
 | |
|         if (this.lineStart > -1) {
 | |
|             let end = Math.min(to, this.lineEnd), last = this.nodes[this.nodes.length - 1];
 | |
|             if (last instanceof HeightMapText)
 | |
|                 last.length += end - this.pos;
 | |
|             else if (end > this.pos || !this.isCovered)
 | |
|                 this.nodes.push(new HeightMapText(end - this.pos, -1));
 | |
|             this.writtenTo = end;
 | |
|             if (to > end) {
 | |
|                 this.nodes.push(null);
 | |
|                 this.writtenTo++;
 | |
|                 this.lineStart = -1;
 | |
|             }
 | |
|         }
 | |
|         this.pos = to;
 | |
|     }
 | |
|     point(from, to, deco) {
 | |
|         if (from < to || deco.heightRelevant) {
 | |
|             let height = deco.widget ? deco.widget.estimatedHeight : 0;
 | |
|             let breaks = deco.widget ? deco.widget.lineBreaks : 0;
 | |
|             if (height < 0)
 | |
|                 height = this.oracle.lineHeight;
 | |
|             let len = to - from;
 | |
|             if (deco.block) {
 | |
|                 this.addBlock(new HeightMapBlock(len, height, deco));
 | |
|             }
 | |
|             else if (len || breaks || height >= relevantWidgetHeight) {
 | |
|                 this.addLineDeco(height, breaks, len);
 | |
|             }
 | |
|         }
 | |
|         else if (to > from) {
 | |
|             this.span(from, to);
 | |
|         }
 | |
|         if (this.lineEnd > -1 && this.lineEnd < this.pos)
 | |
|             this.lineEnd = this.oracle.doc.lineAt(this.pos).to;
 | |
|     }
 | |
|     enterLine() {
 | |
|         if (this.lineStart > -1)
 | |
|             return;
 | |
|         let { from, to } = this.oracle.doc.lineAt(this.pos);
 | |
|         this.lineStart = from;
 | |
|         this.lineEnd = to;
 | |
|         if (this.writtenTo < from) {
 | |
|             if (this.writtenTo < from - 1 || this.nodes[this.nodes.length - 1] == null)
 | |
|                 this.nodes.push(this.blankContent(this.writtenTo, from - 1));
 | |
|             this.nodes.push(null);
 | |
|         }
 | |
|         if (this.pos > from)
 | |
|             this.nodes.push(new HeightMapText(this.pos - from, -1));
 | |
|         this.writtenTo = this.pos;
 | |
|     }
 | |
|     blankContent(from, to) {
 | |
|         let gap = new HeightMapGap(to - from);
 | |
|         if (this.oracle.doc.lineAt(from).to == to)
 | |
|             gap.flags |= 4 /* Flag.SingleLine */;
 | |
|         return gap;
 | |
|     }
 | |
|     ensureLine() {
 | |
|         this.enterLine();
 | |
|         let last = this.nodes.length ? this.nodes[this.nodes.length - 1] : null;
 | |
|         if (last instanceof HeightMapText)
 | |
|             return last;
 | |
|         let line = new HeightMapText(0, -1);
 | |
|         this.nodes.push(line);
 | |
|         return line;
 | |
|     }
 | |
|     addBlock(block) {
 | |
|         this.enterLine();
 | |
|         let deco = block.deco;
 | |
|         if (deco && deco.startSide > 0 && !this.isCovered)
 | |
|             this.ensureLine();
 | |
|         this.nodes.push(block);
 | |
|         this.writtenTo = this.pos = this.pos + block.length;
 | |
|         if (deco && deco.endSide > 0)
 | |
|             this.covering = block;
 | |
|     }
 | |
|     addLineDeco(height, breaks, length) {
 | |
|         let line = this.ensureLine();
 | |
|         line.length += length;
 | |
|         line.collapsed += length;
 | |
|         line.widgetHeight = Math.max(line.widgetHeight, height);
 | |
|         line.breaks += breaks;
 | |
|         this.writtenTo = this.pos = this.pos + length;
 | |
|     }
 | |
|     finish(from) {
 | |
|         let last = this.nodes.length == 0 ? null : this.nodes[this.nodes.length - 1];
 | |
|         if (this.lineStart > -1 && !(last instanceof HeightMapText) && !this.isCovered)
 | |
|             this.nodes.push(new HeightMapText(0, -1));
 | |
|         else if (this.writtenTo < this.pos || last == null)
 | |
|             this.nodes.push(this.blankContent(this.writtenTo, this.pos));
 | |
|         let pos = from;
 | |
|         for (let node of this.nodes) {
 | |
|             if (node instanceof HeightMapText)
 | |
|                 node.updateHeight(this.oracle, pos);
 | |
|             pos += node ? node.length : 1;
 | |
|         }
 | |
|         return this.nodes;
 | |
|     }
 | |
|     // Always called with a region that on both sides either stretches
 | |
|     // to a line break or the end of the document.
 | |
|     // The returned array uses null to indicate line breaks, but never
 | |
|     // starts or ends in a line break, or has multiple line breaks next
 | |
|     // to each other.
 | |
|     static build(oracle, decorations, from, to) {
 | |
|         let builder = new NodeBuilder(from, oracle);
 | |
|         RangeSet.spans(decorations, from, to, builder, 0);
 | |
|         return builder.finish(from);
 | |
|     }
 | |
| }
 | |
| function heightRelevantDecoChanges(a, b, diff) {
 | |
|     let comp = new DecorationComparator;
 | |
|     RangeSet.compare(a, b, diff, comp, 0);
 | |
|     return comp.changes;
 | |
| }
 | |
| class DecorationComparator {
 | |
|     constructor() {
 | |
|         this.changes = [];
 | |
|     }
 | |
|     compareRange() { }
 | |
|     comparePoint(from, to, a, b) {
 | |
|         if (from < to || a && a.heightRelevant || b && b.heightRelevant)
 | |
|             addRange(from, to, this.changes, 5);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function visiblePixelRange(dom, paddingTop) {
 | |
|     let rect = dom.getBoundingClientRect();
 | |
|     let doc = dom.ownerDocument, win = doc.defaultView || window;
 | |
|     let left = Math.max(0, rect.left), right = Math.min(win.innerWidth, rect.right);
 | |
|     let top = Math.max(0, rect.top), bottom = Math.min(win.innerHeight, rect.bottom);
 | |
|     for (let parent = dom.parentNode; parent && parent != doc.body;) {
 | |
|         if (parent.nodeType == 1) {
 | |
|             let elt = parent;
 | |
|             let style = window.getComputedStyle(elt);
 | |
|             if ((elt.scrollHeight > elt.clientHeight || elt.scrollWidth > elt.clientWidth) &&
 | |
|                 style.overflow != "visible") {
 | |
|                 let parentRect = elt.getBoundingClientRect();
 | |
|                 left = Math.max(left, parentRect.left);
 | |
|                 right = Math.min(right, parentRect.right);
 | |
|                 top = Math.max(top, parentRect.top);
 | |
|                 bottom = Math.min(parent == dom.parentNode ? win.innerHeight : bottom, parentRect.bottom);
 | |
|             }
 | |
|             parent = style.position == "absolute" || style.position == "fixed" ? elt.offsetParent : elt.parentNode;
 | |
|         }
 | |
|         else if (parent.nodeType == 11) { // Shadow root
 | |
|             parent = parent.host;
 | |
|         }
 | |
|         else {
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
|     return { left: left - rect.left, right: Math.max(left, right) - rect.left,
 | |
|         top: top - (rect.top + paddingTop), bottom: Math.max(top, bottom) - (rect.top + paddingTop) };
 | |
| }
 | |
| function fullPixelRange(dom, paddingTop) {
 | |
|     let rect = dom.getBoundingClientRect();
 | |
|     return { left: 0, right: rect.right - rect.left,
 | |
|         top: paddingTop, bottom: rect.bottom - (rect.top + paddingTop) };
 | |
| }
 | |
| // Line gaps are placeholder widgets used to hide pieces of overlong
 | |
| // lines within the viewport, as a kludge to keep the editor
 | |
| // responsive when a ridiculously long line is loaded into it.
 | |
| class LineGap {
 | |
|     constructor(from, to, size) {
 | |
|         this.from = from;
 | |
|         this.to = to;
 | |
|         this.size = size;
 | |
|     }
 | |
|     static same(a, b) {
 | |
|         if (a.length != b.length)
 | |
|             return false;
 | |
|         for (let i = 0; i < a.length; i++) {
 | |
|             let gA = a[i], gB = b[i];
 | |
|             if (gA.from != gB.from || gA.to != gB.to || gA.size != gB.size)
 | |
|                 return false;
 | |
|         }
 | |
|         return true;
 | |
|     }
 | |
|     draw(viewState, wrapping) {
 | |
|         return Decoration.replace({
 | |
|             widget: new LineGapWidget(this.size * (wrapping ? viewState.scaleY : viewState.scaleX), wrapping)
 | |
|         }).range(this.from, this.to);
 | |
|     }
 | |
| }
 | |
| class LineGapWidget extends WidgetType {
 | |
|     constructor(size, vertical) {
 | |
|         super();
 | |
|         this.size = size;
 | |
|         this.vertical = vertical;
 | |
|     }
 | |
|     eq(other) { return other.size == this.size && other.vertical == this.vertical; }
 | |
|     toDOM() {
 | |
|         let elt = document.createElement("div");
 | |
|         if (this.vertical) {
 | |
|             elt.style.height = this.size + "px";
 | |
|         }
 | |
|         else {
 | |
|             elt.style.width = this.size + "px";
 | |
|             elt.style.height = "2px";
 | |
|             elt.style.display = "inline-block";
 | |
|         }
 | |
|         return elt;
 | |
|     }
 | |
|     get estimatedHeight() { return this.vertical ? this.size : -1; }
 | |
| }
 | |
| class ViewState {
 | |
|     constructor(state) {
 | |
|         this.state = state;
 | |
|         // These are contentDOM-local coordinates
 | |
|         this.pixelViewport = { left: 0, right: window.innerWidth, top: 0, bottom: 0 };
 | |
|         this.inView = true;
 | |
|         this.paddingTop = 0; // Padding above the document, scaled
 | |
|         this.paddingBottom = 0; // Padding below the document, scaled
 | |
|         this.contentDOMWidth = 0; // contentDOM.getBoundingClientRect().width
 | |
|         this.contentDOMHeight = 0; // contentDOM.getBoundingClientRect().height
 | |
|         this.editorHeight = 0; // scrollDOM.clientHeight, unscaled
 | |
|         this.editorWidth = 0; // scrollDOM.clientWidth, unscaled
 | |
|         this.scrollTop = 0; // Last seen scrollDOM.scrollTop, scaled
 | |
|         this.scrolledToBottom = false;
 | |
|         // The CSS-transformation scale of the editor (transformed size /
 | |
|         // concrete size)
 | |
|         this.scaleX = 1;
 | |
|         this.scaleY = 1;
 | |
|         // The vertical position (document-relative) to which to anchor the
 | |
|         // scroll position. -1 means anchor to the end of the document.
 | |
|         this.scrollAnchorPos = 0;
 | |
|         // The height at the anchor position. Set by the DOM update phase.
 | |
|         // -1 means no height available.
 | |
|         this.scrollAnchorHeight = -1;
 | |
|         // See VP.MaxDOMHeight
 | |
|         this.scaler = IdScaler;
 | |
|         this.scrollTarget = null;
 | |
|         // Briefly set to true when printing, to disable viewport limiting
 | |
|         this.printing = false;
 | |
|         // Flag set when editor content was redrawn, so that the next
 | |
|         // measure stage knows it must read DOM layout
 | |
|         this.mustMeasureContent = true;
 | |
|         this.defaultTextDirection = Direction.LTR;
 | |
|         this.visibleRanges = [];
 | |
|         // Cursor 'assoc' is only significant when the cursor is on a line
 | |
|         // wrap point, where it must stick to the character that it is
 | |
|         // associated with. Since browsers don't provide a reasonable
 | |
|         // interface to set or query this, when a selection is set that
 | |
|         // might cause this to be significant, this flag is set. The next
 | |
|         // measure phase will check whether the cursor is on a line-wrapping
 | |
|         // boundary and, if so, reset it to make sure it is positioned in
 | |
|         // the right place.
 | |
|         this.mustEnforceCursorAssoc = false;
 | |
|         let guessWrapping = state.facet(contentAttributes).some(v => typeof v != "function" && v.class == "cm-lineWrapping");
 | |
|         this.heightOracle = new HeightOracle(guessWrapping);
 | |
|         this.stateDeco = state.facet(decorations).filter(d => typeof d != "function");
 | |
|         this.heightMap = HeightMap.empty().applyChanges(this.stateDeco, Text.empty, this.heightOracle.setDoc(state.doc), [new ChangedRange(0, 0, 0, state.doc.length)]);
 | |
|         for (let i = 0; i < 2; i++) {
 | |
|             this.viewport = this.getViewport(0, null);
 | |
|             if (!this.updateForViewport())
 | |
|                 break;
 | |
|         }
 | |
|         this.updateViewportLines();
 | |
|         this.lineGaps = this.ensureLineGaps([]);
 | |
|         this.lineGapDeco = Decoration.set(this.lineGaps.map(gap => gap.draw(this, false)));
 | |
|         this.computeVisibleRanges();
 | |
|     }
 | |
|     updateForViewport() {
 | |
|         let viewports = [this.viewport], { main } = this.state.selection;
 | |
|         for (let i = 0; i <= 1; i++) {
 | |
|             let pos = i ? main.head : main.anchor;
 | |
|             if (!viewports.some(({ from, to }) => pos >= from && pos <= to)) {
 | |
|                 let { from, to } = this.lineBlockAt(pos);
 | |
|                 viewports.push(new Viewport(from, to));
 | |
|             }
 | |
|         }
 | |
|         this.viewports = viewports.sort((a, b) => a.from - b.from);
 | |
|         return this.updateScaler();
 | |
|     }
 | |
|     updateScaler() {
 | |
|         let scaler = this.scaler;
 | |
|         this.scaler = this.heightMap.height <= 7000000 /* VP.MaxDOMHeight */ ? IdScaler :
 | |
|             new BigScaler(this.heightOracle, this.heightMap, this.viewports);
 | |
|         return scaler.eq(this.scaler) ? 0 : 2 /* UpdateFlag.Height */;
 | |
|     }
 | |
|     updateViewportLines() {
 | |
|         this.viewportLines = [];
 | |
|         this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.heightOracle.setDoc(this.state.doc), 0, 0, block => {
 | |
|             this.viewportLines.push(scaleBlock(block, this.scaler));
 | |
|         });
 | |
|     }
 | |
|     update(update, scrollTarget = null) {
 | |
|         this.state = update.state;
 | |
|         let prevDeco = this.stateDeco;
 | |
|         this.stateDeco = this.state.facet(decorations).filter(d => typeof d != "function");
 | |
|         let contentChanges = update.changedRanges;
 | |
|         let heightChanges = ChangedRange.extendWithRanges(contentChanges, heightRelevantDecoChanges(prevDeco, this.stateDeco, update ? update.changes : ChangeSet.empty(this.state.doc.length)));
 | |
|         let prevHeight = this.heightMap.height;
 | |
|         let scrollAnchor = this.scrolledToBottom ? null : this.scrollAnchorAt(this.scrollTop);
 | |
|         clearHeightChangeFlag();
 | |
|         this.heightMap = this.heightMap.applyChanges(this.stateDeco, update.startState.doc, this.heightOracle.setDoc(this.state.doc), heightChanges);
 | |
|         if (this.heightMap.height != prevHeight || heightChangeFlag)
 | |
|             update.flags |= 2 /* UpdateFlag.Height */;
 | |
|         if (scrollAnchor) {
 | |
|             this.scrollAnchorPos = update.changes.mapPos(scrollAnchor.from, -1);
 | |
|             this.scrollAnchorHeight = scrollAnchor.top;
 | |
|         }
 | |
|         else {
 | |
|             this.scrollAnchorPos = -1;
 | |
|             this.scrollAnchorHeight = this.heightMap.height;
 | |
|         }
 | |
|         let viewport = heightChanges.length ? this.mapViewport(this.viewport, update.changes) : this.viewport;
 | |
|         if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
 | |
|             !this.viewportIsAppropriate(viewport))
 | |
|             viewport = this.getViewport(0, scrollTarget);
 | |
|         let viewportChange = viewport.from != this.viewport.from || viewport.to != this.viewport.to;
 | |
|         this.viewport = viewport;
 | |
|         update.flags |= this.updateForViewport();
 | |
|         if (viewportChange || !update.changes.empty || (update.flags & 2 /* UpdateFlag.Height */))
 | |
|             this.updateViewportLines();
 | |
|         if (this.lineGaps.length || this.viewport.to - this.viewport.from > (2000 /* LG.Margin */ << 1))
 | |
|             this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
 | |
|         update.flags |= this.computeVisibleRanges();
 | |
|         if (scrollTarget)
 | |
|             this.scrollTarget = scrollTarget;
 | |
|         if (!this.mustEnforceCursorAssoc && update.selectionSet && update.view.lineWrapping &&
 | |
|             update.state.selection.main.empty && update.state.selection.main.assoc &&
 | |
|             !update.state.facet(nativeSelectionHidden))
 | |
|             this.mustEnforceCursorAssoc = true;
 | |
|     }
 | |
|     measure(view) {
 | |
|         let dom = view.contentDOM, style = window.getComputedStyle(dom);
 | |
|         let oracle = this.heightOracle;
 | |
|         let whiteSpace = style.whiteSpace;
 | |
|         this.defaultTextDirection = style.direction == "rtl" ? Direction.RTL : Direction.LTR;
 | |
|         let refresh = this.heightOracle.mustRefreshForWrapping(whiteSpace);
 | |
|         let domRect = dom.getBoundingClientRect();
 | |
|         let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != domRect.height;
 | |
|         this.contentDOMHeight = domRect.height;
 | |
|         this.mustMeasureContent = false;
 | |
|         let result = 0, bias = 0;
 | |
|         if (domRect.width && domRect.height) {
 | |
|             let { scaleX, scaleY } = getScale(dom, domRect);
 | |
|             if (scaleX > .005 && Math.abs(this.scaleX - scaleX) > .005 ||
 | |
|                 scaleY > .005 && Math.abs(this.scaleY - scaleY) > .005) {
 | |
|                 this.scaleX = scaleX;
 | |
|                 this.scaleY = scaleY;
 | |
|                 result |= 8 /* UpdateFlag.Geometry */;
 | |
|                 refresh = measureContent = true;
 | |
|             }
 | |
|         }
 | |
|         // Vertical padding
 | |
|         let paddingTop = (parseInt(style.paddingTop) || 0) * this.scaleY;
 | |
|         let paddingBottom = (parseInt(style.paddingBottom) || 0) * this.scaleY;
 | |
|         if (this.paddingTop != paddingTop || this.paddingBottom != paddingBottom) {
 | |
|             this.paddingTop = paddingTop;
 | |
|             this.paddingBottom = paddingBottom;
 | |
|             result |= 8 /* UpdateFlag.Geometry */ | 2 /* UpdateFlag.Height */;
 | |
|         }
 | |
|         if (this.editorWidth != view.scrollDOM.clientWidth) {
 | |
|             if (oracle.lineWrapping)
 | |
|                 measureContent = true;
 | |
|             this.editorWidth = view.scrollDOM.clientWidth;
 | |
|             result |= 8 /* UpdateFlag.Geometry */;
 | |
|         }
 | |
|         let scrollTop = view.scrollDOM.scrollTop * this.scaleY;
 | |
|         if (this.scrollTop != scrollTop) {
 | |
|             this.scrollAnchorHeight = -1;
 | |
|             this.scrollTop = scrollTop;
 | |
|         }
 | |
|         this.scrolledToBottom = isScrolledToBottom(view.scrollDOM);
 | |
|         // Pixel viewport
 | |
|         let pixelViewport = (this.printing ? fullPixelRange : visiblePixelRange)(dom, this.paddingTop);
 | |
|         let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
 | |
|         this.pixelViewport = pixelViewport;
 | |
|         let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
 | |
|         if (inView != this.inView) {
 | |
|             this.inView = inView;
 | |
|             if (inView)
 | |
|                 measureContent = true;
 | |
|         }
 | |
|         if (!this.inView && !this.scrollTarget)
 | |
|             return 0;
 | |
|         let contentWidth = domRect.width;
 | |
|         if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight) {
 | |
|             this.contentDOMWidth = domRect.width;
 | |
|             this.editorHeight = view.scrollDOM.clientHeight;
 | |
|             result |= 8 /* UpdateFlag.Geometry */;
 | |
|         }
 | |
|         if (measureContent) {
 | |
|             let lineHeights = view.docView.measureVisibleLineHeights(this.viewport);
 | |
|             if (oracle.mustRefreshForHeights(lineHeights))
 | |
|                 refresh = true;
 | |
|             if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
 | |
|                 let { lineHeight, charWidth, textHeight } = view.docView.measureTextSize();
 | |
|                 refresh = lineHeight > 0 && oracle.refresh(whiteSpace, lineHeight, charWidth, textHeight, contentWidth / charWidth, lineHeights);
 | |
|                 if (refresh) {
 | |
|                     view.docView.minWidth = 0;
 | |
|                     result |= 8 /* UpdateFlag.Geometry */;
 | |
|                 }
 | |
|             }
 | |
|             if (dTop > 0 && dBottom > 0)
 | |
|                 bias = Math.max(dTop, dBottom);
 | |
|             else if (dTop < 0 && dBottom < 0)
 | |
|                 bias = Math.min(dTop, dBottom);
 | |
|             clearHeightChangeFlag();
 | |
|             for (let vp of this.viewports) {
 | |
|                 let heights = vp.from == this.viewport.from ? lineHeights : view.docView.measureVisibleLineHeights(vp);
 | |
|                 this.heightMap = (refresh ? HeightMap.empty().applyChanges(this.stateDeco, Text.empty, this.heightOracle, [new ChangedRange(0, 0, 0, view.state.doc.length)]) : this.heightMap).updateHeight(oracle, 0, refresh, new MeasuredHeights(vp.from, heights));
 | |
|             }
 | |
|             if (heightChangeFlag)
 | |
|                 result |= 2 /* UpdateFlag.Height */;
 | |
|         }
 | |
|         let viewportChange = !this.viewportIsAppropriate(this.viewport, bias) ||
 | |
|             this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from ||
 | |
|                 this.scrollTarget.range.head > this.viewport.to);
 | |
|         if (viewportChange) {
 | |
|             if (result & 2 /* UpdateFlag.Height */)
 | |
|                 result |= this.updateScaler();
 | |
|             this.viewport = this.getViewport(bias, this.scrollTarget);
 | |
|             result |= this.updateForViewport();
 | |
|         }
 | |
|         if ((result & 2 /* UpdateFlag.Height */) || viewportChange)
 | |
|             this.updateViewportLines();
 | |
|         if (this.lineGaps.length || this.viewport.to - this.viewport.from > (2000 /* LG.Margin */ << 1))
 | |
|             this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps, view));
 | |
|         result |= this.computeVisibleRanges();
 | |
|         if (this.mustEnforceCursorAssoc) {
 | |
|             this.mustEnforceCursorAssoc = false;
 | |
|             // This is done in the read stage, because moving the selection
 | |
|             // to a line end is going to trigger a layout anyway, so it
 | |
|             // can't be a pure write. It should be rare that it does any
 | |
|             // writing.
 | |
|             view.docView.enforceCursorAssoc();
 | |
|         }
 | |
|         return result;
 | |
|     }
 | |
|     get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top); }
 | |
|     get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom); }
 | |
|     getViewport(bias, scrollTarget) {
 | |
|         // This will divide VP.Margin between the top and the
 | |
|         // bottom, depending on the bias (the change in viewport position
 | |
|         // since the last update). It'll hold a number between 0 and 1
 | |
|         let marginTop = 0.5 - Math.max(-0.5, Math.min(0.5, bias / 1000 /* VP.Margin */ / 2));
 | |
|         let map = this.heightMap, oracle = this.heightOracle;
 | |
|         let { visibleTop, visibleBottom } = this;
 | |
|         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);
 | |
|         // If scrollTarget is given, make sure the viewport includes that position
 | |
|         if (scrollTarget) {
 | |
|             let { head } = scrollTarget.range;
 | |
|             if (head < viewport.from || head > viewport.to) {
 | |
|                 let viewHeight = Math.min(this.editorHeight, this.pixelViewport.bottom - this.pixelViewport.top);
 | |
|                 let block = map.lineAt(head, QueryType.ByPos, oracle, 0, 0), topPos;
 | |
|                 if (scrollTarget.y == "center")
 | |
|                     topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
 | |
|                 else if (scrollTarget.y == "start" || scrollTarget.y == "nearest" && head < viewport.from)
 | |
|                     topPos = block.top;
 | |
|                 else
 | |
|                     topPos = block.bottom - viewHeight;
 | |
|                 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);
 | |
|             }
 | |
|         }
 | |
|         return viewport;
 | |
|     }
 | |
|     mapViewport(viewport, changes) {
 | |
|         let from = changes.mapPos(viewport.from, -1), to = changes.mapPos(viewport.to, 1);
 | |
|         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);
 | |
|     }
 | |
|     // Checks if a given viewport covers the visible part of the
 | |
|     // document and not too much beyond that.
 | |
|     viewportIsAppropriate({ from, to }, bias = 0) {
 | |
|         if (!this.inView)
 | |
|             return true;
 | |
|         let { top } = this.heightMap.lineAt(from, QueryType.ByPos, this.heightOracle, 0, 0);
 | |
|         let { bottom } = this.heightMap.lineAt(to, QueryType.ByPos, this.heightOracle, 0, 0);
 | |
|         let { visibleTop, visibleBottom } = this;
 | |
|         return (from == 0 || top <= visibleTop - Math.max(10 /* VP.MinCoverMargin */, Math.min(-bias, 250 /* VP.MaxCoverMargin */))) &&
 | |
|             (to == this.state.doc.length ||
 | |
|                 bottom >= visibleBottom + Math.max(10 /* VP.MinCoverMargin */, Math.min(bias, 250 /* VP.MaxCoverMargin */))) &&
 | |
|             (top > visibleTop - 2 * 1000 /* VP.Margin */ && bottom < visibleBottom + 2 * 1000 /* VP.Margin */);
 | |
|     }
 | |
|     mapLineGaps(gaps, changes) {
 | |
|         if (!gaps.length || changes.empty)
 | |
|             return gaps;
 | |
|         let mapped = [];
 | |
|         for (let gap of gaps)
 | |
|             if (!changes.touchesRange(gap.from, gap.to))
 | |
|                 mapped.push(new LineGap(changes.mapPos(gap.from), changes.mapPos(gap.to), gap.size));
 | |
|         return mapped;
 | |
|     }
 | |
|     // Computes positions in the viewport where the start or end of a
 | |
|     // line should be hidden, trying to reuse existing line gaps when
 | |
|     // appropriate to avoid unneccesary redraws.
 | |
|     // Uses crude character-counting for the positioning and sizing,
 | |
|     // since actual DOM coordinates aren't always available and
 | |
|     // predictable. Relies on generous margins (see LG.Margin) to hide
 | |
|     // the artifacts this might produce from the user.
 | |
|     ensureLineGaps(current, mayMeasure) {
 | |
|         let wrapping = this.heightOracle.lineWrapping;
 | |
|         let margin = wrapping ? 10000 /* LG.MarginWrap */ : 2000 /* LG.Margin */, halfMargin = margin >> 1, doubleMargin = margin << 1;
 | |
|         // The non-wrapping logic won't work at all in predominantly right-to-left text.
 | |
|         if (this.defaultTextDirection != Direction.LTR && !wrapping)
 | |
|             return [];
 | |
|         let gaps = [];
 | |
|         let addGap = (from, to, line, structure) => {
 | |
|             if (to - from < halfMargin)
 | |
|                 return;
 | |
|             let sel = this.state.selection.main, avoid = [sel.from];
 | |
|             if (!sel.empty)
 | |
|                 avoid.push(sel.to);
 | |
|             for (let pos of avoid) {
 | |
|                 if (pos > from && pos < to) {
 | |
|                     addGap(from, pos - 10 /* LG.SelectionMargin */, line, structure);
 | |
|                     addGap(pos + 10 /* LG.SelectionMargin */, to, line, structure);
 | |
|                     return;
 | |
|                 }
 | |
|             }
 | |
|             let gap = find(current, gap => gap.from >= line.from && gap.to <= line.to &&
 | |
|                 Math.abs(gap.from - from) < halfMargin && Math.abs(gap.to - to) < halfMargin &&
 | |
|                 !avoid.some(pos => gap.from < pos && gap.to > pos));
 | |
|             if (!gap) {
 | |
|                 // When scrolling down, snap gap ends to line starts to avoid shifts in wrapping
 | |
|                 if (to < line.to && mayMeasure && wrapping &&
 | |
|                     mayMeasure.visibleRanges.some(r => r.from <= to && r.to >= to)) {
 | |
|                     let lineStart = mayMeasure.moveToLineBoundary(EditorSelection.cursor(to), false, true).head;
 | |
|                     if (lineStart > from)
 | |
|                         to = lineStart;
 | |
|                 }
 | |
|                 gap = new LineGap(from, to, this.gapSize(line, from, to, structure));
 | |
|             }
 | |
|             gaps.push(gap);
 | |
|         };
 | |
|         let checkLine = (line) => {
 | |
|             if (line.length < doubleMargin || line.type != BlockType.Text)
 | |
|                 return;
 | |
|             let structure = lineStructure(line.from, line.to, this.stateDeco);
 | |
|             if (structure.total < doubleMargin)
 | |
|                 return;
 | |
|             let target = this.scrollTarget ? this.scrollTarget.range.head : null;
 | |
|             let viewFrom, viewTo;
 | |
|             if (wrapping) {
 | |
|                 let marginHeight = (margin / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
 | |
|                 let top, bot;
 | |
|                 if (target != null) {
 | |
|                     let targetFrac = findFraction(structure, target);
 | |
|                     let spaceFrac = ((this.visibleBottom - this.visibleTop) / 2 + marginHeight) / line.height;
 | |
|                     top = targetFrac - spaceFrac;
 | |
|                     bot = targetFrac + spaceFrac;
 | |
|                 }
 | |
|                 else {
 | |
|                     top = (this.visibleTop - line.top - marginHeight) / line.height;
 | |
|                     bot = (this.visibleBottom - line.top + marginHeight) / line.height;
 | |
|                 }
 | |
|                 viewFrom = findPosition(structure, top);
 | |
|                 viewTo = findPosition(structure, bot);
 | |
|             }
 | |
|             else {
 | |
|                 let totalWidth = structure.total * this.heightOracle.charWidth;
 | |
|                 let marginWidth = margin * this.heightOracle.charWidth;
 | |
|                 let left, right;
 | |
|                 if (target != null) {
 | |
|                     let targetFrac = findFraction(structure, target);
 | |
|                     let spaceFrac = ((this.pixelViewport.right - this.pixelViewport.left) / 2 + marginWidth) / totalWidth;
 | |
|                     left = targetFrac - spaceFrac;
 | |
|                     right = targetFrac + spaceFrac;
 | |
|                 }
 | |
|                 else {
 | |
|                     left = (this.pixelViewport.left - marginWidth) / totalWidth;
 | |
|                     right = (this.pixelViewport.right + marginWidth) / totalWidth;
 | |
|                 }
 | |
|                 viewFrom = findPosition(structure, left);
 | |
|                 viewTo = findPosition(structure, right);
 | |
|             }
 | |
|             if (viewFrom > line.from)
 | |
|                 addGap(line.from, viewFrom, line, structure);
 | |
|             if (viewTo < line.to)
 | |
|                 addGap(viewTo, line.to, line, structure);
 | |
|         };
 | |
|         for (let line of this.viewportLines) {
 | |
|             if (Array.isArray(line.type))
 | |
|                 line.type.forEach(checkLine);
 | |
|             else
 | |
|                 checkLine(line);
 | |
|         }
 | |
|         return gaps;
 | |
|     }
 | |
|     gapSize(line, from, to, structure) {
 | |
|         let fraction = findFraction(structure, to) - findFraction(structure, from);
 | |
|         if (this.heightOracle.lineWrapping) {
 | |
|             return line.height * fraction;
 | |
|         }
 | |
|         else {
 | |
|             return structure.total * this.heightOracle.charWidth * fraction;
 | |
|         }
 | |
|     }
 | |
|     updateLineGaps(gaps) {
 | |
|         if (!LineGap.same(gaps, this.lineGaps)) {
 | |
|             this.lineGaps = gaps;
 | |
|             this.lineGapDeco = Decoration.set(gaps.map(gap => gap.draw(this, this.heightOracle.lineWrapping)));
 | |
|         }
 | |
|     }
 | |
|     computeVisibleRanges() {
 | |
|         let deco = this.stateDeco;
 | |
|         if (this.lineGaps.length)
 | |
|             deco = deco.concat(this.lineGapDeco);
 | |
|         let ranges = [];
 | |
|         RangeSet.spans(deco, this.viewport.from, this.viewport.to, {
 | |
|             span(from, to) { ranges.push({ from, to }); },
 | |
|             point() { }
 | |
|         }, 20);
 | |
|         let changed = ranges.length != this.visibleRanges.length ||
 | |
|             this.visibleRanges.some((r, i) => r.from != ranges[i].from || r.to != ranges[i].to);
 | |
|         this.visibleRanges = ranges;
 | |
|         return changed ? 4 /* UpdateFlag.Viewport */ : 0;
 | |
|     }
 | |
|     lineBlockAt(pos) {
 | |
|         return (pos >= this.viewport.from && pos <= this.viewport.to &&
 | |
|             this.viewportLines.find(b => b.from <= pos && b.to >= pos)) ||
 | |
|             scaleBlock(this.heightMap.lineAt(pos, QueryType.ByPos, this.heightOracle, 0, 0), this.scaler);
 | |
|     }
 | |
|     lineBlockAtHeight(height) {
 | |
|         return (height >= this.viewportLines[0].top && height <= this.viewportLines[this.viewportLines.length - 1].bottom &&
 | |
|             this.viewportLines.find(l => l.top <= height && l.bottom >= height)) ||
 | |
|             scaleBlock(this.heightMap.lineAt(this.scaler.fromDOM(height), QueryType.ByHeight, this.heightOracle, 0, 0), this.scaler);
 | |
|     }
 | |
|     scrollAnchorAt(scrollTop) {
 | |
|         let block = this.lineBlockAtHeight(scrollTop + 8);
 | |
|         return block.from >= this.viewport.from || this.viewportLines[0].top - scrollTop > 200 ? block : this.viewportLines[0];
 | |
|     }
 | |
|     elementAtHeight(height) {
 | |
|         return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height), this.heightOracle, 0, 0), this.scaler);
 | |
|     }
 | |
|     get docHeight() {
 | |
|         return this.scaler.toDOM(this.heightMap.height);
 | |
|     }
 | |
|     get contentHeight() {
 | |
|         return this.docHeight + this.paddingTop + this.paddingBottom;
 | |
|     }
 | |
| }
 | |
| class Viewport {
 | |
|     constructor(from, to) {
 | |
|         this.from = from;
 | |
|         this.to = to;
 | |
|     }
 | |
| }
 | |
| function lineStructure(from, to, stateDeco) {
 | |
|     let ranges = [], pos = from, total = 0;
 | |
|     RangeSet.spans(stateDeco, from, to, {
 | |
|         span() { },
 | |
|         point(from, to) {
 | |
|             if (from > pos) {
 | |
|                 ranges.push({ from: pos, to: from });
 | |
|                 total += from - pos;
 | |
|             }
 | |
|             pos = to;
 | |
|         }
 | |
|     }, 20); // We're only interested in collapsed ranges of a significant size
 | |
|     if (pos < to) {
 | |
|         ranges.push({ from: pos, to });
 | |
|         total += to - pos;
 | |
|     }
 | |
|     return { total, ranges };
 | |
| }
 | |
| function findPosition({ total, ranges }, ratio) {
 | |
|     if (ratio <= 0)
 | |
|         return ranges[0].from;
 | |
|     if (ratio >= 1)
 | |
|         return ranges[ranges.length - 1].to;
 | |
|     let dist = Math.floor(total * ratio);
 | |
|     for (let i = 0;; i++) {
 | |
|         let { from, to } = ranges[i], size = to - from;
 | |
|         if (dist <= size)
 | |
|             return from + dist;
 | |
|         dist -= size;
 | |
|     }
 | |
| }
 | |
| function findFraction(structure, pos) {
 | |
|     let counted = 0;
 | |
|     for (let { from, to } of structure.ranges) {
 | |
|         if (pos <= to) {
 | |
|             counted += pos - from;
 | |
|             break;
 | |
|         }
 | |
|         counted += to - from;
 | |
|     }
 | |
|     return counted / structure.total;
 | |
| }
 | |
| function find(array, f) {
 | |
|     for (let val of array)
 | |
|         if (f(val))
 | |
|             return val;
 | |
|     return undefined;
 | |
| }
 | |
| // Don't scale when the document height is within the range of what
 | |
| // the DOM can handle.
 | |
| const IdScaler = {
 | |
|     toDOM(n) { return n; },
 | |
|     fromDOM(n) { return n; },
 | |
|     scale: 1,
 | |
|     eq(other) { return other == this; }
 | |
| };
 | |
| // When the height is too big (> VP.MaxDOMHeight), scale down the
 | |
| // regions outside the viewports so that the total height is
 | |
| // VP.MaxDOMHeight.
 | |
| class BigScaler {
 | |
|     constructor(oracle, heightMap, viewports) {
 | |
|         let vpHeight = 0, base = 0, domBase = 0;
 | |
|         this.viewports = viewports.map(({ from, to }) => {
 | |
|             let top = heightMap.lineAt(from, QueryType.ByPos, oracle, 0, 0).top;
 | |
|             let bottom = heightMap.lineAt(to, QueryType.ByPos, oracle, 0, 0).bottom;
 | |
|             vpHeight += bottom - top;
 | |
|             return { from, to, top, bottom, domTop: 0, domBottom: 0 };
 | |
|         });
 | |
|         this.scale = (7000000 /* VP.MaxDOMHeight */ - vpHeight) / (heightMap.height - vpHeight);
 | |
|         for (let obj of this.viewports) {
 | |
|             obj.domTop = domBase + (obj.top - base) * this.scale;
 | |
|             domBase = obj.domBottom = obj.domTop + (obj.bottom - obj.top);
 | |
|             base = obj.bottom;
 | |
|         }
 | |
|     }
 | |
|     toDOM(n) {
 | |
|         for (let i = 0, base = 0, domBase = 0;; i++) {
 | |
|             let vp = i < this.viewports.length ? this.viewports[i] : null;
 | |
|             if (!vp || n < vp.top)
 | |
|                 return domBase + (n - base) * this.scale;
 | |
|             if (n <= vp.bottom)
 | |
|                 return vp.domTop + (n - vp.top);
 | |
|             base = vp.bottom;
 | |
|             domBase = vp.domBottom;
 | |
|         }
 | |
|     }
 | |
|     fromDOM(n) {
 | |
|         for (let i = 0, base = 0, domBase = 0;; i++) {
 | |
|             let vp = i < this.viewports.length ? this.viewports[i] : null;
 | |
|             if (!vp || n < vp.domTop)
 | |
|                 return base + (n - domBase) / this.scale;
 | |
|             if (n <= vp.domBottom)
 | |
|                 return vp.top + (n - vp.domTop);
 | |
|             base = vp.bottom;
 | |
|             domBase = vp.domBottom;
 | |
|         }
 | |
|     }
 | |
|     eq(other) {
 | |
|         if (!(other instanceof BigScaler))
 | |
|             return false;
 | |
|         return this.scale == other.scale && this.viewports.length == other.viewports.length &&
 | |
|             this.viewports.every((vp, i) => vp.from == other.viewports[i].from && vp.to == other.viewports[i].to);
 | |
|     }
 | |
| }
 | |
| function scaleBlock(block, scaler) {
 | |
|     if (scaler.scale == 1)
 | |
|         return block;
 | |
|     let bTop = scaler.toDOM(block.top), bBottom = scaler.toDOM(block.bottom);
 | |
|     return new BlockInfo(block.from, block.length, bTop, bBottom - bTop, Array.isArray(block._content) ? block._content.map(b => scaleBlock(b, scaler)) : block._content);
 | |
| }
 | |
| 
 | |
| const theme = /*@__PURE__*/Facet.define({ combine: strs => strs.join(" ") });
 | |
| const darkTheme = /*@__PURE__*/Facet.define({ combine: values => values.indexOf(true) > -1 });
 | |
| const baseThemeID = /*@__PURE__*/StyleModule.newName(), baseLightID = /*@__PURE__*/StyleModule.newName(), baseDarkID = /*@__PURE__*/StyleModule.newName();
 | |
| const lightDarkIDs = { "&light": "." + baseLightID, "&dark": "." + baseDarkID };
 | |
| function buildTheme(main, spec, scopes) {
 | |
|     return new StyleModule(spec, {
 | |
|         finish(sel) {
 | |
|             return /&/.test(sel) ? sel.replace(/&\w*/, m => {
 | |
|                 if (m == "&")
 | |
|                     return main;
 | |
|                 if (!scopes || !scopes[m])
 | |
|                     throw new RangeError(`Unsupported selector: ${m}`);
 | |
|                 return scopes[m];
 | |
|             }) : main + " " + sel;
 | |
|         }
 | |
|     });
 | |
| }
 | |
| const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
 | |
|     "&": {
 | |
|         position: "relative !important",
 | |
|         boxSizing: "border-box",
 | |
|         "&.cm-focused": {
 | |
|             // Provide a simple default outline to make sure a focused
 | |
|             // editor is visually distinct. Can't leave the default behavior
 | |
|             // because that will apply to the content element, which is
 | |
|             // inside the scrollable container and doesn't include the
 | |
|             // gutters. We also can't use an 'auto' outline, since those
 | |
|             // are, for some reason, drawn behind the element content, which
 | |
|             // will cause things like the active line background to cover
 | |
|             // the outline (#297).
 | |
|             outline: "1px dotted #212121"
 | |
|         },
 | |
|         display: "flex !important",
 | |
|         flexDirection: "column"
 | |
|     },
 | |
|     ".cm-scroller": {
 | |
|         display: "flex !important",
 | |
|         alignItems: "flex-start !important",
 | |
|         fontFamily: "monospace",
 | |
|         lineHeight: 1.4,
 | |
|         height: "100%",
 | |
|         overflowX: "auto",
 | |
|         position: "relative",
 | |
|         zIndex: 0,
 | |
|         overflowAnchor: "none",
 | |
|     },
 | |
|     ".cm-content": {
 | |
|         margin: 0,
 | |
|         flexGrow: 2,
 | |
|         flexShrink: 0,
 | |
|         display: "block",
 | |
|         whiteSpace: "pre",
 | |
|         wordWrap: "normal", // https://github.com/codemirror/dev/issues/456
 | |
|         boxSizing: "border-box",
 | |
|         minHeight: "100%",
 | |
|         padding: "4px 0",
 | |
|         outline: "none",
 | |
|         "&[contenteditable=true]": {
 | |
|             WebkitUserModify: "read-write-plaintext-only",
 | |
|         }
 | |
|     },
 | |
|     ".cm-lineWrapping": {
 | |
|         whiteSpace_fallback: "pre-wrap", // For IE
 | |
|         whiteSpace: "break-spaces",
 | |
|         wordBreak: "break-word", // For Safari, which doesn't support overflow-wrap: anywhere
 | |
|         overflowWrap: "anywhere",
 | |
|         flexShrink: 1
 | |
|     },
 | |
|     "&light .cm-content": { caretColor: "black" },
 | |
|     "&dark .cm-content": { caretColor: "white" },
 | |
|     ".cm-line": {
 | |
|         display: "block",
 | |
|         padding: "0 2px 0 6px"
 | |
|     },
 | |
|     ".cm-layer": {
 | |
|         position: "absolute",
 | |
|         left: 0,
 | |
|         top: 0,
 | |
|         contain: "size style",
 | |
|         "& > *": {
 | |
|             position: "absolute"
 | |
|         }
 | |
|     },
 | |
|     "&light .cm-selectionBackground": {
 | |
|         background: "#d9d9d9"
 | |
|     },
 | |
|     "&dark .cm-selectionBackground": {
 | |
|         background: "#222"
 | |
|     },
 | |
|     "&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
 | |
|         background: "#d7d4f0"
 | |
|     },
 | |
|     "&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
 | |
|         background: "#233"
 | |
|     },
 | |
|     ".cm-cursorLayer": {
 | |
|         pointerEvents: "none"
 | |
|     },
 | |
|     "&.cm-focused > .cm-scroller > .cm-cursorLayer": {
 | |
|         animation: "steps(1) cm-blink 1.2s infinite"
 | |
|     },
 | |
|     // Two animations defined so that we can switch between them to
 | |
|     // restart the animation without forcing another style
 | |
|     // recomputation.
 | |
|     "@keyframes cm-blink": { "0%": {}, "50%": { opacity: 0 }, "100%": {} },
 | |
|     "@keyframes cm-blink2": { "0%": {}, "50%": { opacity: 0 }, "100%": {} },
 | |
|     ".cm-cursor, .cm-dropCursor": {
 | |
|         borderLeft: "1.2px solid black",
 | |
|         marginLeft: "-0.6px",
 | |
|         pointerEvents: "none",
 | |
|     },
 | |
|     ".cm-cursor": {
 | |
|         display: "none"
 | |
|     },
 | |
|     "&dark .cm-cursor": {
 | |
|         borderLeftColor: "#444"
 | |
|     },
 | |
|     ".cm-dropCursor": {
 | |
|         position: "absolute"
 | |
|     },
 | |
|     "&.cm-focused > .cm-scroller > .cm-cursorLayer .cm-cursor": {
 | |
|         display: "block"
 | |
|     },
 | |
|     ".cm-iso": {
 | |
|         unicodeBidi: "isolate"
 | |
|     },
 | |
|     ".cm-announced": {
 | |
|         position: "fixed",
 | |
|         top: "-10000px"
 | |
|     },
 | |
|     "@media print": {
 | |
|         ".cm-announced": { display: "none" }
 | |
|     },
 | |
|     "&light .cm-activeLine": { backgroundColor: "#cceeff44" },
 | |
|     "&dark .cm-activeLine": { backgroundColor: "#99eeff33" },
 | |
|     "&light .cm-specialChar": { color: "red" },
 | |
|     "&dark .cm-specialChar": { color: "#f78" },
 | |
|     ".cm-gutters": {
 | |
|         flexShrink: 0,
 | |
|         display: "flex",
 | |
|         height: "100%",
 | |
|         boxSizing: "border-box",
 | |
|         insetInlineStart: 0,
 | |
|         zIndex: 200
 | |
|     },
 | |
|     "&light .cm-gutters": {
 | |
|         backgroundColor: "#f5f5f5",
 | |
|         color: "#6c6c6c",
 | |
|         borderRight: "1px solid #ddd"
 | |
|     },
 | |
|     "&dark .cm-gutters": {
 | |
|         backgroundColor: "#333338",
 | |
|         color: "#ccc"
 | |
|     },
 | |
|     ".cm-gutter": {
 | |
|         display: "flex !important", // Necessary -- prevents margin collapsing
 | |
|         flexDirection: "column",
 | |
|         flexShrink: 0,
 | |
|         boxSizing: "border-box",
 | |
|         minHeight: "100%",
 | |
|         overflow: "hidden"
 | |
|     },
 | |
|     ".cm-gutterElement": {
 | |
|         boxSizing: "border-box"
 | |
|     },
 | |
|     ".cm-lineNumbers .cm-gutterElement": {
 | |
|         padding: "0 3px 0 5px",
 | |
|         minWidth: "20px",
 | |
|         textAlign: "right",
 | |
|         whiteSpace: "nowrap"
 | |
|     },
 | |
|     "&light .cm-activeLineGutter": {
 | |
|         backgroundColor: "#e2f2ff"
 | |
|     },
 | |
|     "&dark .cm-activeLineGutter": {
 | |
|         backgroundColor: "#222227"
 | |
|     },
 | |
|     ".cm-panels": {
 | |
|         boxSizing: "border-box",
 | |
|         position: "sticky",
 | |
|         left: 0,
 | |
|         right: 0,
 | |
|         zIndex: 300
 | |
|     },
 | |
|     "&light .cm-panels": {
 | |
|         backgroundColor: "#f5f5f5",
 | |
|         color: "black"
 | |
|     },
 | |
|     "&light .cm-panels-top": {
 | |
|         borderBottom: "1px solid #ddd"
 | |
|     },
 | |
|     "&light .cm-panels-bottom": {
 | |
|         borderTop: "1px solid #ddd"
 | |
|     },
 | |
|     "&dark .cm-panels": {
 | |
|         backgroundColor: "#333338",
 | |
|         color: "white"
 | |
|     },
 | |
|     ".cm-tab": {
 | |
|         display: "inline-block",
 | |
|         overflow: "hidden",
 | |
|         verticalAlign: "bottom"
 | |
|     },
 | |
|     ".cm-widgetBuffer": {
 | |
|         verticalAlign: "text-top",
 | |
|         height: "1em",
 | |
|         width: 0,
 | |
|         display: "inline"
 | |
|     },
 | |
|     ".cm-placeholder": {
 | |
|         color: "#888",
 | |
|         display: "inline-block",
 | |
|         verticalAlign: "top",
 | |
|     },
 | |
|     ".cm-highlightSpace:before": {
 | |
|         content: "attr(data-display)",
 | |
|         position: "absolute",
 | |
|         pointerEvents: "none",
 | |
|         color: "#888"
 | |
|     },
 | |
|     ".cm-highlightTab": {
 | |
|         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>')`,
 | |
|         backgroundSize: "auto 100%",
 | |
|         backgroundPosition: "right 90%",
 | |
|         backgroundRepeat: "no-repeat"
 | |
|     },
 | |
|     ".cm-trailingSpace": {
 | |
|         backgroundColor: "#ff332255"
 | |
|     },
 | |
|     ".cm-button": {
 | |
|         verticalAlign: "middle",
 | |
|         color: "inherit",
 | |
|         fontSize: "70%",
 | |
|         padding: ".2em 1em",
 | |
|         borderRadius: "1px"
 | |
|     },
 | |
|     "&light .cm-button": {
 | |
|         backgroundImage: "linear-gradient(#eff1f5, #d9d9df)",
 | |
|         border: "1px solid #888",
 | |
|         "&:active": {
 | |
|             backgroundImage: "linear-gradient(#b4b4b4, #d0d3d6)"
 | |
|         }
 | |
|     },
 | |
|     "&dark .cm-button": {
 | |
|         backgroundImage: "linear-gradient(#393939, #111)",
 | |
|         border: "1px solid #888",
 | |
|         "&:active": {
 | |
|             backgroundImage: "linear-gradient(#111, #333)"
 | |
|         }
 | |
|     },
 | |
|     ".cm-textfield": {
 | |
|         verticalAlign: "middle",
 | |
|         color: "inherit",
 | |
|         fontSize: "70%",
 | |
|         border: "1px solid silver",
 | |
|         padding: ".2em .5em"
 | |
|     },
 | |
|     "&light .cm-textfield": {
 | |
|         backgroundColor: "white"
 | |
|     },
 | |
|     "&dark .cm-textfield": {
 | |
|         border: "1px solid #555",
 | |
|         backgroundColor: "inherit"
 | |
|     }
 | |
| }, lightDarkIDs);
 | |
| 
 | |
| const observeOptions = {
 | |
|     childList: true,
 | |
|     characterData: true,
 | |
|     subtree: true,
 | |
|     attributes: true,
 | |
|     characterDataOldValue: true
 | |
| };
 | |
| // IE11 has very broken mutation observers, so we also listen to
 | |
| // DOMCharacterDataModified there
 | |
| const useCharData = browser.ie && browser.ie_version <= 11;
 | |
| class DOMObserver {
 | |
|     constructor(view) {
 | |
|         this.view = view;
 | |
|         this.active = false;
 | |
|         this.editContext = null;
 | |
|         // The known selection. Kept in our own object, as opposed to just
 | |
|         // directly accessing the selection because:
 | |
|         //  - Safari doesn't report the right selection in shadow DOM
 | |
|         //  - Reading from the selection forces a DOM layout
 | |
|         //  - This way, we can ignore selectionchange events if we have
 | |
|         //    already seen the 'new' selection
 | |
|         this.selectionRange = new DOMSelectionState;
 | |
|         // Set when a selection change is detected, cleared on flush
 | |
|         this.selectionChanged = false;
 | |
|         this.delayedFlush = -1;
 | |
|         this.resizeTimeout = -1;
 | |
|         this.queue = [];
 | |
|         this.delayedAndroidKey = null;
 | |
|         this.flushingAndroidKey = -1;
 | |
|         this.lastChange = 0;
 | |
|         this.scrollTargets = [];
 | |
|         this.intersection = null;
 | |
|         this.resizeScroll = null;
 | |
|         this.intersecting = false;
 | |
|         this.gapIntersection = null;
 | |
|         this.gaps = [];
 | |
|         this.printQuery = null;
 | |
|         // Timeout for scheduling check of the parents that need scroll handlers
 | |
|         this.parentCheck = -1;
 | |
|         this.dom = view.contentDOM;
 | |
|         this.observer = new MutationObserver(mutations => {
 | |
|             for (let mut of mutations)
 | |
|                 this.queue.push(mut);
 | |
|             // IE11 will sometimes (on typing over a selection or
 | |
|             // backspacing out a single character text node) call the
 | |
|             // observer callback before actually updating the DOM.
 | |
|             //
 | |
|             // Unrelatedly, iOS Safari will, when ending a composition,
 | |
|             // sometimes first clear it, deliver the mutations, and then
 | |
|             // reinsert the finished text. CodeMirror's handling of the
 | |
|             // deletion will prevent the reinsertion from happening,
 | |
|             // breaking composition.
 | |
|             if ((browser.ie && browser.ie_version <= 11 || browser.ios && view.composing) &&
 | |
|                 mutations.some(m => m.type == "childList" && m.removedNodes.length ||
 | |
|                     m.type == "characterData" && m.oldValue.length > m.target.nodeValue.length))
 | |
|                 this.flushSoon();
 | |
|             else
 | |
|                 this.flush();
 | |
|         });
 | |
|         if (window.EditContext && view.constructor.EDIT_CONTEXT !== false &&
 | |
|             // Chrome <126 doesn't support inverted selections in edit context (#1392)
 | |
|             !(browser.chrome && browser.chrome_version < 126)) {
 | |
|             this.editContext = new EditContextManager(view);
 | |
|             if (view.state.facet(editable))
 | |
|                 view.contentDOM.editContext = this.editContext.editContext;
 | |
|         }
 | |
|         if (useCharData)
 | |
|             this.onCharData = (event) => {
 | |
|                 this.queue.push({ target: event.target,
 | |
|                     type: "characterData",
 | |
|                     oldValue: event.prevValue });
 | |
|                 this.flushSoon();
 | |
|             };
 | |
|         this.onSelectionChange = this.onSelectionChange.bind(this);
 | |
|         this.onResize = this.onResize.bind(this);
 | |
|         this.onPrint = this.onPrint.bind(this);
 | |
|         this.onScroll = this.onScroll.bind(this);
 | |
|         if (window.matchMedia)
 | |
|             this.printQuery = window.matchMedia("print");
 | |
|         if (typeof ResizeObserver == "function") {
 | |
|             this.resizeScroll = new ResizeObserver(() => {
 | |
|                 var _a;
 | |
|                 if (((_a = this.view.docView) === null || _a === void 0 ? void 0 : _a.lastUpdate) < Date.now() - 75)
 | |
|                     this.onResize();
 | |
|             });
 | |
|             this.resizeScroll.observe(view.scrollDOM);
 | |
|         }
 | |
|         this.addWindowListeners(this.win = view.win);
 | |
|         this.start();
 | |
|         if (typeof IntersectionObserver == "function") {
 | |
|             this.intersection = new IntersectionObserver(entries => {
 | |
|                 if (this.parentCheck < 0)
 | |
|                     this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1000);
 | |
|                 if (entries.length > 0 && (entries[entries.length - 1].intersectionRatio > 0) != this.intersecting) {
 | |
|                     this.intersecting = !this.intersecting;
 | |
|                     if (this.intersecting != this.view.inView)
 | |
|                         this.onScrollChanged(document.createEvent("Event"));
 | |
|                 }
 | |
|             }, { threshold: [0, .001] });
 | |
|             this.intersection.observe(this.dom);
 | |
|             this.gapIntersection = new IntersectionObserver(entries => {
 | |
|                 if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0)
 | |
|                     this.onScrollChanged(document.createEvent("Event"));
 | |
|             }, {});
 | |
|         }
 | |
|         this.listenForScroll();
 | |
|         this.readSelectionRange();
 | |
|     }
 | |
|     onScrollChanged(e) {
 | |
|         this.view.inputState.runHandlers("scroll", e);
 | |
|         if (this.intersecting)
 | |
|             this.view.measure();
 | |
|     }
 | |
|     onScroll(e) {
 | |
|         if (this.intersecting)
 | |
|             this.flush(false);
 | |
|         if (this.editContext)
 | |
|             this.view.requestMeasure(this.editContext.measureReq);
 | |
|         this.onScrollChanged(e);
 | |
|     }
 | |
|     onResize() {
 | |
|         if (this.resizeTimeout < 0)
 | |
|             this.resizeTimeout = setTimeout(() => {
 | |
|                 this.resizeTimeout = -1;
 | |
|                 this.view.requestMeasure();
 | |
|             }, 50);
 | |
|     }
 | |
|     onPrint(event) {
 | |
|         if ((event.type == "change" || !event.type) && !event.matches)
 | |
|             return;
 | |
|         this.view.viewState.printing = true;
 | |
|         this.view.measure();
 | |
|         setTimeout(() => {
 | |
|             this.view.viewState.printing = false;
 | |
|             this.view.requestMeasure();
 | |
|         }, 500);
 | |
|     }
 | |
|     updateGaps(gaps) {
 | |
|         if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
 | |
|             this.gapIntersection.disconnect();
 | |
|             for (let gap of gaps)
 | |
|                 this.gapIntersection.observe(gap);
 | |
|             this.gaps = gaps;
 | |
|         }
 | |
|     }
 | |
|     onSelectionChange(event) {
 | |
|         let wasChanged = this.selectionChanged;
 | |
|         if (!this.readSelectionRange() || this.delayedAndroidKey)
 | |
|             return;
 | |
|         let { view } = this, sel = this.selectionRange;
 | |
|         if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
 | |
|             return;
 | |
|         let context = sel.anchorNode && view.docView.nearest(sel.anchorNode);
 | |
|         if (context && context.ignoreEvent(event)) {
 | |
|             if (!wasChanged)
 | |
|                 this.selectionChanged = false;
 | |
|             return;
 | |
|         }
 | |
|         // Deletions on IE11 fire their events in the wrong order, giving
 | |
|         // us a selection change event before the DOM changes are
 | |
|         // reported.
 | |
|         // Chrome Android has a similar issue when backspacing out a
 | |
|         // selection (#645).
 | |
|         if ((browser.ie && browser.ie_version <= 11 || browser.android && browser.chrome) && !view.state.selection.main.empty &&
 | |
|             // (Selection.isCollapsed isn't reliable on IE)
 | |
|             sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset))
 | |
|             this.flushSoon();
 | |
|         else
 | |
|             this.flush(false);
 | |
|     }
 | |
|     readSelectionRange() {
 | |
|         let { view } = this;
 | |
|         // The Selection object is broken in shadow roots in Safari. See
 | |
|         // https://github.com/codemirror/dev/issues/414
 | |
|         let selection = getSelection(view.root);
 | |
|         if (!selection)
 | |
|             return false;
 | |
|         let range = browser.safari && view.root.nodeType == 11 &&
 | |
|             deepActiveElement(this.dom.ownerDocument) == this.dom &&
 | |
|             safariSelectionRangeHack(this.view, selection) || selection;
 | |
|         if (!range || this.selectionRange.eq(range))
 | |
|             return false;
 | |
|         let local = hasSelection(this.dom, range);
 | |
|         // Detect the situation where the browser has, on focus, moved the
 | |
|         // selection to the start of the content element. Reset it to the
 | |
|         // position from the editor state.
 | |
|         if (local && !this.selectionChanged &&
 | |
|             view.inputState.lastFocusTime > Date.now() - 200 &&
 | |
|             view.inputState.lastTouchTime < Date.now() - 300 &&
 | |
|             atElementStart(this.dom, range)) {
 | |
|             this.view.inputState.lastFocusTime = 0;
 | |
|             view.docView.updateSelection();
 | |
|             return false;
 | |
|         }
 | |
|         this.selectionRange.setRange(range);
 | |
|         if (local)
 | |
|             this.selectionChanged = true;
 | |
|         return true;
 | |
|     }
 | |
|     setSelectionRange(anchor, head) {
 | |
|         this.selectionRange.set(anchor.node, anchor.offset, head.node, head.offset);
 | |
|         this.selectionChanged = false;
 | |
|     }
 | |
|     clearSelectionRange() {
 | |
|         this.selectionRange.set(null, 0, null, 0);
 | |
|     }
 | |
|     listenForScroll() {
 | |
|         this.parentCheck = -1;
 | |
|         let i = 0, changed = null;
 | |
|         for (let dom = this.dom; dom;) {
 | |
|             if (dom.nodeType == 1) {
 | |
|                 if (!changed && i < this.scrollTargets.length && this.scrollTargets[i] == dom)
 | |
|                     i++;
 | |
|                 else if (!changed)
 | |
|                     changed = this.scrollTargets.slice(0, i);
 | |
|                 if (changed)
 | |
|                     changed.push(dom);
 | |
|                 dom = dom.assignedSlot || dom.parentNode;
 | |
|             }
 | |
|             else if (dom.nodeType == 11) { // Shadow root
 | |
|                 dom = dom.host;
 | |
|             }
 | |
|             else {
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|         if (i < this.scrollTargets.length && !changed)
 | |
|             changed = this.scrollTargets.slice(0, i);
 | |
|         if (changed) {
 | |
|             for (let dom of this.scrollTargets)
 | |
|                 dom.removeEventListener("scroll", this.onScroll);
 | |
|             for (let dom of this.scrollTargets = changed)
 | |
|                 dom.addEventListener("scroll", this.onScroll);
 | |
|         }
 | |
|     }
 | |
|     ignore(f) {
 | |
|         if (!this.active)
 | |
|             return f();
 | |
|         try {
 | |
|             this.stop();
 | |
|             return f();
 | |
|         }
 | |
|         finally {
 | |
|             this.start();
 | |
|             this.clear();
 | |
|         }
 | |
|     }
 | |
|     start() {
 | |
|         if (this.active)
 | |
|             return;
 | |
|         this.observer.observe(this.dom, observeOptions);
 | |
|         if (useCharData)
 | |
|             this.dom.addEventListener("DOMCharacterDataModified", this.onCharData);
 | |
|         this.active = true;
 | |
|     }
 | |
|     stop() {
 | |
|         if (!this.active)
 | |
|             return;
 | |
|         this.active = false;
 | |
|         this.observer.disconnect();
 | |
|         if (useCharData)
 | |
|             this.dom.removeEventListener("DOMCharacterDataModified", this.onCharData);
 | |
|     }
 | |
|     // Throw away any pending changes
 | |
|     clear() {
 | |
|         this.processRecords();
 | |
|         this.queue.length = 0;
 | |
|         this.selectionChanged = false;
 | |
|     }
 | |
|     // Chrome Android, especially in combination with GBoard, not only
 | |
|     // doesn't reliably fire regular key events, but also often
 | |
|     // surrounds the effect of enter or backspace with a bunch of
 | |
|     // composition events that, when interrupted, cause text duplication
 | |
|     // or other kinds of corruption. This hack makes the editor back off
 | |
|     // from handling DOM changes for a moment when such a key is
 | |
|     // detected (via beforeinput or keydown), and then tries to flush
 | |
|     // them or, if that has no effect, dispatches the given key.
 | |
|     delayAndroidKey(key, keyCode) {
 | |
|         var _a;
 | |
|         if (!this.delayedAndroidKey) {
 | |
|             let flush = () => {
 | |
|                 let key = this.delayedAndroidKey;
 | |
|                 if (key) {
 | |
|                     this.clearDelayedAndroidKey();
 | |
|                     this.view.inputState.lastKeyCode = key.keyCode;
 | |
|                     this.view.inputState.lastKeyTime = Date.now();
 | |
|                     let flushed = this.flush();
 | |
|                     if (!flushed && key.force)
 | |
|                         dispatchKey(this.dom, key.key, key.keyCode);
 | |
|                 }
 | |
|             };
 | |
|             this.flushingAndroidKey = this.view.win.requestAnimationFrame(flush);
 | |
|         }
 | |
|         // Since backspace beforeinput is sometimes signalled spuriously,
 | |
|         // Enter always takes precedence.
 | |
|         if (!this.delayedAndroidKey || key == "Enter")
 | |
|             this.delayedAndroidKey = {
 | |
|                 key, keyCode,
 | |
|                 // Only run the key handler when no changes are detected if
 | |
|                 // this isn't coming right after another change, in which case
 | |
|                 // it is probably part of a weird chain of updates, and should
 | |
|                 // be ignored if it returns the DOM to its previous state.
 | |
|                 force: this.lastChange < Date.now() - 50 || !!((_a = this.delayedAndroidKey) === null || _a === void 0 ? void 0 : _a.force)
 | |
|             };
 | |
|     }
 | |
|     clearDelayedAndroidKey() {
 | |
|         this.win.cancelAnimationFrame(this.flushingAndroidKey);
 | |
|         this.delayedAndroidKey = null;
 | |
|         this.flushingAndroidKey = -1;
 | |
|     }
 | |
|     flushSoon() {
 | |
|         if (this.delayedFlush < 0)
 | |
|             this.delayedFlush = this.view.win.requestAnimationFrame(() => { this.delayedFlush = -1; this.flush(); });
 | |
|     }
 | |
|     forceFlush() {
 | |
|         if (this.delayedFlush >= 0) {
 | |
|             this.view.win.cancelAnimationFrame(this.delayedFlush);
 | |
|             this.delayedFlush = -1;
 | |
|         }
 | |
|         this.flush();
 | |
|     }
 | |
|     pendingRecords() {
 | |
|         for (let mut of this.observer.takeRecords())
 | |
|             this.queue.push(mut);
 | |
|         return this.queue;
 | |
|     }
 | |
|     processRecords() {
 | |
|         let records = this.pendingRecords();
 | |
|         if (records.length)
 | |
|             this.queue = [];
 | |
|         let from = -1, to = -1, typeOver = false;
 | |
|         for (let record of records) {
 | |
|             let range = this.readMutation(record);
 | |
|             if (!range)
 | |
|                 continue;
 | |
|             if (range.typeOver)
 | |
|                 typeOver = true;
 | |
|             if (from == -1) {
 | |
|                 ({ from, to } = range);
 | |
|             }
 | |
|             else {
 | |
|                 from = Math.min(range.from, from);
 | |
|                 to = Math.max(range.to, to);
 | |
|             }
 | |
|         }
 | |
|         return { from, to, typeOver };
 | |
|     }
 | |
|     readChange() {
 | |
|         let { from, to, typeOver } = this.processRecords();
 | |
|         let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
 | |
|         if (from < 0 && !newSel)
 | |
|             return null;
 | |
|         if (from > -1)
 | |
|             this.lastChange = Date.now();
 | |
|         this.view.inputState.lastFocusTime = 0;
 | |
|         this.selectionChanged = false;
 | |
|         let change = new DOMChange(this.view, from, to, typeOver);
 | |
|         this.view.docView.domChanged = { newSel: change.newSel ? change.newSel.main : null };
 | |
|         return change;
 | |
|     }
 | |
|     // Apply pending changes, if any
 | |
|     flush(readSelection = true) {
 | |
|         // Completely hold off flushing when pending keys are set—the code
 | |
|         // managing those will make sure processRecords is called and the
 | |
|         // view is resynchronized after
 | |
|         if (this.delayedFlush >= 0 || this.delayedAndroidKey)
 | |
|             return false;
 | |
|         if (readSelection)
 | |
|             this.readSelectionRange();
 | |
|         let domChange = this.readChange();
 | |
|         if (!domChange) {
 | |
|             this.view.requestMeasure();
 | |
|             return false;
 | |
|         }
 | |
|         let startState = this.view.state;
 | |
|         let handled = applyDOMChange(this.view, domChange);
 | |
|         // The view wasn't updated but DOM/selection changes were seen. Reset the view.
 | |
|         if (this.view.state == startState &&
 | |
|             (domChange.domChanged || domChange.newSel && !domChange.newSel.main.eq(this.view.state.selection.main)))
 | |
|             this.view.update([]);
 | |
|         return handled;
 | |
|     }
 | |
|     readMutation(rec) {
 | |
|         let cView = this.view.docView.nearest(rec.target);
 | |
|         if (!cView || cView.ignoreMutation(rec))
 | |
|             return null;
 | |
|         cView.markDirty(rec.type == "attributes");
 | |
|         if (rec.type == "attributes")
 | |
|             cView.flags |= 4 /* ViewFlag.AttrsDirty */;
 | |
|         if (rec.type == "childList") {
 | |
|             let childBefore = findChild(cView, rec.previousSibling || rec.target.previousSibling, -1);
 | |
|             let childAfter = findChild(cView, rec.nextSibling || rec.target.nextSibling, 1);
 | |
|             return { from: childBefore ? cView.posAfter(childBefore) : cView.posAtStart,
 | |
|                 to: childAfter ? cView.posBefore(childAfter) : cView.posAtEnd, typeOver: false };
 | |
|         }
 | |
|         else if (rec.type == "characterData") {
 | |
|             return { from: cView.posAtStart, to: cView.posAtEnd, typeOver: rec.target.nodeValue == rec.oldValue };
 | |
|         }
 | |
|         else {
 | |
|             return null;
 | |
|         }
 | |
|     }
 | |
|     setWindow(win) {
 | |
|         if (win != this.win) {
 | |
|             this.removeWindowListeners(this.win);
 | |
|             this.win = win;
 | |
|             this.addWindowListeners(this.win);
 | |
|         }
 | |
|     }
 | |
|     addWindowListeners(win) {
 | |
|         win.addEventListener("resize", this.onResize);
 | |
|         if (this.printQuery) {
 | |
|             if (this.printQuery.addEventListener)
 | |
|                 this.printQuery.addEventListener("change", this.onPrint);
 | |
|             else
 | |
|                 this.printQuery.addListener(this.onPrint);
 | |
|         }
 | |
|         else
 | |
|             win.addEventListener("beforeprint", this.onPrint);
 | |
|         win.addEventListener("scroll", this.onScroll);
 | |
|         win.document.addEventListener("selectionchange", this.onSelectionChange);
 | |
|     }
 | |
|     removeWindowListeners(win) {
 | |
|         win.removeEventListener("scroll", this.onScroll);
 | |
|         win.removeEventListener("resize", this.onResize);
 | |
|         if (this.printQuery) {
 | |
|             if (this.printQuery.removeEventListener)
 | |
|                 this.printQuery.removeEventListener("change", this.onPrint);
 | |
|             else
 | |
|                 this.printQuery.removeListener(this.onPrint);
 | |
|         }
 | |
|         else
 | |
|             win.removeEventListener("beforeprint", this.onPrint);
 | |
|         win.document.removeEventListener("selectionchange", this.onSelectionChange);
 | |
|     }
 | |
|     update(update) {
 | |
|         if (this.editContext) {
 | |
|             this.editContext.update(update);
 | |
|             if (update.startState.facet(editable) != update.state.facet(editable))
 | |
|                 update.view.contentDOM.editContext = update.state.facet(editable) ? this.editContext.editContext : null;
 | |
|         }
 | |
|     }
 | |
|     destroy() {
 | |
|         var _a, _b, _c;
 | |
|         this.stop();
 | |
|         (_a = this.intersection) === null || _a === void 0 ? void 0 : _a.disconnect();
 | |
|         (_b = this.gapIntersection) === null || _b === void 0 ? void 0 : _b.disconnect();
 | |
|         (_c = this.resizeScroll) === null || _c === void 0 ? void 0 : _c.disconnect();
 | |
|         for (let dom of this.scrollTargets)
 | |
|             dom.removeEventListener("scroll", this.onScroll);
 | |
|         this.removeWindowListeners(this.win);
 | |
|         clearTimeout(this.parentCheck);
 | |
|         clearTimeout(this.resizeTimeout);
 | |
|         this.win.cancelAnimationFrame(this.delayedFlush);
 | |
|         this.win.cancelAnimationFrame(this.flushingAndroidKey);
 | |
|         if (this.editContext) {
 | |
|             this.view.contentDOM.editContext = null;
 | |
|             this.editContext.destroy();
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function findChild(cView, dom, dir) {
 | |
|     while (dom) {
 | |
|         let curView = ContentView.get(dom);
 | |
|         if (curView && curView.parent == cView)
 | |
|             return curView;
 | |
|         let parent = dom.parentNode;
 | |
|         dom = parent != cView.dom ? parent : dir > 0 ? dom.nextSibling : dom.previousSibling;
 | |
|     }
 | |
|     return null;
 | |
| }
 | |
| function buildSelectionRangeFromRange(view, range) {
 | |
|     let anchorNode = range.startContainer, anchorOffset = range.startOffset;
 | |
|     let focusNode = range.endContainer, focusOffset = range.endOffset;
 | |
|     let curAnchor = view.docView.domAtPos(view.state.selection.main.anchor);
 | |
|     // Since such a range doesn't distinguish between anchor and head,
 | |
|     // use a heuristic that flips it around if its end matches the
 | |
|     // current anchor.
 | |
|     if (isEquivalentPosition(curAnchor.node, curAnchor.offset, focusNode, focusOffset))
 | |
|         [anchorNode, anchorOffset, focusNode, focusOffset] = [focusNode, focusOffset, anchorNode, anchorOffset];
 | |
|     return { anchorNode, anchorOffset, focusNode, focusOffset };
 | |
| }
 | |
| // Used to work around a Safari Selection/shadow DOM bug (#414)
 | |
| function safariSelectionRangeHack(view, selection) {
 | |
|     if (selection.getComposedRanges) {
 | |
|         let range = selection.getComposedRanges(view.root)[0];
 | |
|         if (range)
 | |
|             return buildSelectionRangeFromRange(view, range);
 | |
|     }
 | |
|     let found = null;
 | |
|     // Because Safari (at least in 2018-2021) doesn't provide regular
 | |
|     // access to the selection inside a shadowroot, we have to perform a
 | |
|     // ridiculous hack to get at it—using `execCommand` to trigger a
 | |
|     // `beforeInput` event so that we can read the target range from the
 | |
|     // event.
 | |
|     function read(event) {
 | |
|         event.preventDefault();
 | |
|         event.stopImmediatePropagation();
 | |
|         found = event.getTargetRanges()[0];
 | |
|     }
 | |
|     view.contentDOM.addEventListener("beforeinput", read, true);
 | |
|     view.dom.ownerDocument.execCommand("indent");
 | |
|     view.contentDOM.removeEventListener("beforeinput", read, true);
 | |
|     return found ? buildSelectionRangeFromRange(view, found) : null;
 | |
| }
 | |
| class EditContextManager {
 | |
|     constructor(view) {
 | |
|         // The document window for which the text in the context is
 | |
|         // maintained. For large documents, this may be smaller than the
 | |
|         // editor document. This window always includes the selection head.
 | |
|         this.from = 0;
 | |
|         this.to = 0;
 | |
|         // When applying a transaction, this is used to compare the change
 | |
|         // made to the context content to the change in the transaction in
 | |
|         // order to make the minimal changes to the context (since touching
 | |
|         // that sometimes breaks series of multiple edits made for a single
 | |
|         // user action on some Android keyboards)
 | |
|         this.pendingContextChange = null;
 | |
|         this.handlers = Object.create(null);
 | |
|         this.resetRange(view.state);
 | |
|         let context = this.editContext = new window.EditContext({
 | |
|             text: view.state.doc.sliceString(this.from, this.to),
 | |
|             selectionStart: this.toContextPos(Math.max(this.from, Math.min(this.to, view.state.selection.main.anchor))),
 | |
|             selectionEnd: this.toContextPos(view.state.selection.main.head)
 | |
|         });
 | |
|         this.handlers.textupdate = e => {
 | |
|             let { anchor } = view.state.selection.main;
 | |
|             let change = { from: this.toEditorPos(e.updateRangeStart),
 | |
|                 to: this.toEditorPos(e.updateRangeEnd),
 | |
|                 insert: Text.of(e.text.split("\n")) };
 | |
|             // If the window doesn't include the anchor, assume changes
 | |
|             // adjacent to a side go up to the anchor.
 | |
|             if (change.from == this.from && anchor < this.from)
 | |
|                 change.from = anchor;
 | |
|             else if (change.to == this.to && anchor > this.to)
 | |
|                 change.to = anchor;
 | |
|             // Edit contexts sometimes fire empty changes
 | |
|             if (change.from == change.to && !change.insert.length)
 | |
|                 return;
 | |
|             this.pendingContextChange = change;
 | |
|             if (!view.state.readOnly)
 | |
|                 applyDOMChangeInner(view, change, EditorSelection.single(this.toEditorPos(e.selectionStart), this.toEditorPos(e.selectionEnd)));
 | |
|             // If the transaction didn't flush our change, revert it so
 | |
|             // that the context is in sync with the editor state again.
 | |
|             if (this.pendingContextChange) {
 | |
|                 this.revertPending(view.state);
 | |
|                 this.setSelection(view.state);
 | |
|             }
 | |
|         };
 | |
|         this.handlers.characterboundsupdate = e => {
 | |
|             let rects = [], prev = null;
 | |
|             for (let i = this.toEditorPos(e.rangeStart), end = this.toEditorPos(e.rangeEnd); i < end; i++) {
 | |
|                 let rect = view.coordsForChar(i);
 | |
|                 prev = (rect && new DOMRect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top))
 | |
|                     || prev || new DOMRect;
 | |
|                 rects.push(prev);
 | |
|             }
 | |
|             context.updateCharacterBounds(e.rangeStart, rects);
 | |
|         };
 | |
|         this.handlers.textformatupdate = e => {
 | |
|             let deco = [];
 | |
|             for (let format of e.getTextFormats()) {
 | |
|                 let lineStyle = format.underlineStyle, thickness = format.underlineThickness;
 | |
|                 if (lineStyle != "None" && thickness != "None") {
 | |
|                     let style = `text-decoration: underline ${lineStyle == "Dashed" ? "dashed " : lineStyle == "Squiggle" ? "wavy " : ""}${thickness == "Thin" ? 1 : 2}px`;
 | |
|                     deco.push(Decoration.mark({ attributes: { style } })
 | |
|                         .range(this.toEditorPos(format.rangeStart), this.toEditorPos(format.rangeEnd)));
 | |
|                 }
 | |
|             }
 | |
|             view.dispatch({ effects: setEditContextFormatting.of(Decoration.set(deco)) });
 | |
|         };
 | |
|         this.handlers.compositionstart = () => {
 | |
|             if (view.inputState.composing < 0) {
 | |
|                 view.inputState.composing = 0;
 | |
|                 view.inputState.compositionFirstChange = true;
 | |
|             }
 | |
|         };
 | |
|         this.handlers.compositionend = () => {
 | |
|             view.inputState.composing = -1;
 | |
|             view.inputState.compositionFirstChange = null;
 | |
|         };
 | |
|         for (let event in this.handlers)
 | |
|             context.addEventListener(event, this.handlers[event]);
 | |
|         this.measureReq = { read: view => {
 | |
|                 this.editContext.updateControlBounds(view.contentDOM.getBoundingClientRect());
 | |
|                 let sel = getSelection(view.root);
 | |
|                 if (sel && sel.rangeCount)
 | |
|                     this.editContext.updateSelectionBounds(sel.getRangeAt(0).getBoundingClientRect());
 | |
|             } };
 | |
|     }
 | |
|     applyEdits(update) {
 | |
|         let off = 0, abort = false, pending = this.pendingContextChange;
 | |
|         update.changes.iterChanges((fromA, toA, _fromB, _toB, insert) => {
 | |
|             if (abort)
 | |
|                 return;
 | |
|             let dLen = insert.length - (toA - fromA);
 | |
|             if (pending && toA >= pending.to) {
 | |
|                 if (pending.from == fromA && pending.to == toA && pending.insert.eq(insert)) {
 | |
|                     pending = this.pendingContextChange = null; // Match
 | |
|                     off += dLen;
 | |
|                     this.to += dLen;
 | |
|                     return;
 | |
|                 }
 | |
|                 else { // Mismatch, revert
 | |
|                     pending = null;
 | |
|                     this.revertPending(update.state);
 | |
|                 }
 | |
|             }
 | |
|             fromA += off;
 | |
|             toA += off;
 | |
|             if (toA <= this.from) { // Before the window
 | |
|                 this.from += dLen;
 | |
|                 this.to += dLen;
 | |
|             }
 | |
|             else if (fromA < this.to) { // Overlaps with window
 | |
|                 if (fromA < this.from || toA > this.to || (this.to - this.from) + insert.length > 30000 /* CxVp.MaxSize */) {
 | |
|                     abort = true;
 | |
|                     return;
 | |
|                 }
 | |
|                 this.editContext.updateText(this.toContextPos(fromA), this.toContextPos(toA), insert.toString());
 | |
|                 this.to += dLen;
 | |
|             }
 | |
|             off += dLen;
 | |
|         });
 | |
|         if (pending && !abort)
 | |
|             this.revertPending(update.state);
 | |
|         return !abort;
 | |
|     }
 | |
|     update(update) {
 | |
|         let reverted = this.pendingContextChange;
 | |
|         if (!this.applyEdits(update) || !this.rangeIsValid(update.state)) {
 | |
|             this.pendingContextChange = null;
 | |
|             this.resetRange(update.state);
 | |
|             this.editContext.updateText(0, this.editContext.text.length, update.state.doc.sliceString(this.from, this.to));
 | |
|             this.setSelection(update.state);
 | |
|         }
 | |
|         else if (update.docChanged || update.selectionSet || reverted) {
 | |
|             this.setSelection(update.state);
 | |
|         }
 | |
|         if (update.geometryChanged || update.docChanged || update.selectionSet)
 | |
|             update.view.requestMeasure(this.measureReq);
 | |
|     }
 | |
|     resetRange(state) {
 | |
|         let { head } = state.selection.main;
 | |
|         this.from = Math.max(0, head - 10000 /* CxVp.Margin */);
 | |
|         this.to = Math.min(state.doc.length, head + 10000 /* CxVp.Margin */);
 | |
|     }
 | |
|     revertPending(state) {
 | |
|         let pending = this.pendingContextChange;
 | |
|         this.pendingContextChange = null;
 | |
|         this.editContext.updateText(this.toContextPos(pending.from), this.toContextPos(pending.from + pending.insert.length), state.doc.sliceString(pending.from, pending.to));
 | |
|     }
 | |
|     setSelection(state) {
 | |
|         let { main } = state.selection;
 | |
|         let start = this.toContextPos(Math.max(this.from, Math.min(this.to, main.anchor)));
 | |
|         let end = this.toContextPos(main.head);
 | |
|         if (this.editContext.selectionStart != start || this.editContext.selectionEnd != end)
 | |
|             this.editContext.updateSelection(start, end);
 | |
|     }
 | |
|     rangeIsValid(state) {
 | |
|         let { head } = state.selection.main;
 | |
|         return !(this.from > 0 && head - this.from < 500 /* CxVp.MinMargin */ ||
 | |
|             this.to < state.doc.length && this.to - head < 500 /* CxVp.MinMargin */ ||
 | |
|             this.to - this.from > 10000 /* CxVp.Margin */ * 3);
 | |
|     }
 | |
|     toEditorPos(contextPos) { return contextPos + this.from; }
 | |
|     toContextPos(editorPos) { return editorPos - this.from; }
 | |
|     destroy() {
 | |
|         for (let event in this.handlers)
 | |
|             this.editContext.removeEventListener(event, this.handlers[event]);
 | |
|     }
 | |
| }
 | |
| 
 | |
| // The editor's update state machine looks something like this:
 | |
| //
 | |
| //     Idle → Updating ⇆ Idle (unchecked) → Measuring → Idle
 | |
| //                                         ↑      ↓
 | |
| //                                         Updating (measure)
 | |
| //
 | |
| // The difference between 'Idle' and 'Idle (unchecked)' lies in
 | |
| // whether a layout check has been scheduled. A regular update through
 | |
| // the `update` method updates the DOM in a write-only fashion, and
 | |
| // relies on a check (scheduled with `requestAnimationFrame`) to make
 | |
| // sure everything is where it should be and the viewport covers the
 | |
| // visible code. That check continues to measure and then optionally
 | |
| // update until it reaches a coherent state.
 | |
| /**
 | |
| An editor view represents the editor's user interface. It holds
 | |
| the editable DOM surface, and possibly other elements such as the
 | |
| line number gutter. It handles events and dispatches state
 | |
| transactions for editing actions.
 | |
| */
 | |
| class EditorView {
 | |
|     /**
 | |
|     The current editor state.
 | |
|     */
 | |
|     get state() { return this.viewState.state; }
 | |
|     /**
 | |
|     To be able to display large documents without consuming too much
 | |
|     memory or overloading the browser, CodeMirror only draws the
 | |
|     code that is visible (plus a margin around it) to the DOM. This
 | |
|     property tells you the extent of the current drawn viewport, in
 | |
|     document positions.
 | |
|     */
 | |
|     get viewport() { return this.viewState.viewport; }
 | |
|     /**
 | |
|     When there are, for example, large collapsed ranges in the
 | |
|     viewport, its size can be a lot bigger than the actual visible
 | |
|     content. Thus, if you are doing something like styling the
 | |
|     content in the viewport, it is preferable to only do so for
 | |
|     these ranges, which are the subset of the viewport that is
 | |
|     actually drawn.
 | |
|     */
 | |
|     get visibleRanges() { return this.viewState.visibleRanges; }
 | |
|     /**
 | |
|     Returns false when the editor is entirely scrolled out of view
 | |
|     or otherwise hidden.
 | |
|     */
 | |
|     get inView() { return this.viewState.inView; }
 | |
|     /**
 | |
|     Indicates whether the user is currently composing text via
 | |
|     [IME](https://en.wikipedia.org/wiki/Input_method), and at least
 | |
|     one change has been made in the current composition.
 | |
|     */
 | |
|     get composing() { return this.inputState.composing > 0; }
 | |
|     /**
 | |
|     Indicates whether the user is currently in composing state. Note
 | |
|     that on some platforms, like Android, this will be the case a
 | |
|     lot, since just putting the cursor on a word starts a
 | |
|     composition there.
 | |
|     */
 | |
|     get compositionStarted() { return this.inputState.composing >= 0; }
 | |
|     /**
 | |
|     The document or shadow root that the view lives in.
 | |
|     */
 | |
|     get root() { return this._root; }
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     get win() { return this.dom.ownerDocument.defaultView || window; }
 | |
|     /**
 | |
|     Construct a new view. You'll want to either provide a `parent`
 | |
|     option, or put `view.dom` into your document after creating a
 | |
|     view, so that the user can see the editor.
 | |
|     */
 | |
|     constructor(config = {}) {
 | |
|         var _a;
 | |
|         this.plugins = [];
 | |
|         this.pluginMap = new Map;
 | |
|         this.editorAttrs = {};
 | |
|         this.contentAttrs = {};
 | |
|         this.bidiCache = [];
 | |
|         this.destroyed = false;
 | |
|         /**
 | |
|         @internal
 | |
|         */
 | |
|         this.updateState = 2 /* UpdateState.Updating */;
 | |
|         /**
 | |
|         @internal
 | |
|         */
 | |
|         this.measureScheduled = -1;
 | |
|         /**
 | |
|         @internal
 | |
|         */
 | |
|         this.measureRequests = [];
 | |
|         this.contentDOM = document.createElement("div");
 | |
|         this.scrollDOM = document.createElement("div");
 | |
|         this.scrollDOM.tabIndex = -1;
 | |
|         this.scrollDOM.className = "cm-scroller";
 | |
|         this.scrollDOM.appendChild(this.contentDOM);
 | |
|         this.announceDOM = document.createElement("div");
 | |
|         this.announceDOM.className = "cm-announced";
 | |
|         this.announceDOM.setAttribute("aria-live", "polite");
 | |
|         this.dom = document.createElement("div");
 | |
|         this.dom.appendChild(this.announceDOM);
 | |
|         this.dom.appendChild(this.scrollDOM);
 | |
|         if (config.parent)
 | |
|             config.parent.appendChild(this.dom);
 | |
|         let { dispatch } = config;
 | |
|         this.dispatchTransactions = config.dispatchTransactions ||
 | |
|             (dispatch && ((trs) => trs.forEach(tr => dispatch(tr, this)))) ||
 | |
|             ((trs) => this.update(trs));
 | |
|         this.dispatch = this.dispatch.bind(this);
 | |
|         this._root = (config.root || getRoot(config.parent) || document);
 | |
|         this.viewState = new ViewState(config.state || EditorState.create(config));
 | |
|         if (config.scrollTo && config.scrollTo.is(scrollIntoView))
 | |
|             this.viewState.scrollTarget = config.scrollTo.value.clip(this.viewState.state);
 | |
|         this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec));
 | |
|         for (let plugin of this.plugins)
 | |
|             plugin.update(this);
 | |
|         this.observer = new DOMObserver(this);
 | |
|         this.inputState = new InputState(this);
 | |
|         this.inputState.ensureHandlers(this.plugins);
 | |
|         this.docView = new DocView(this);
 | |
|         this.mountStyles();
 | |
|         this.updateAttrs();
 | |
|         this.updateState = 0 /* UpdateState.Idle */;
 | |
|         this.requestMeasure();
 | |
|         if ((_a = document.fonts) === null || _a === void 0 ? void 0 : _a.ready)
 | |
|             document.fonts.ready.then(() => this.requestMeasure());
 | |
|     }
 | |
|     dispatch(...input) {
 | |
|         let trs = input.length == 1 && input[0] instanceof Transaction ? input
 | |
|             : input.length == 1 && Array.isArray(input[0]) ? input[0]
 | |
|                 : [this.state.update(...input)];
 | |
|         this.dispatchTransactions(trs, this);
 | |
|     }
 | |
|     /**
 | |
|     Update the view for the given array of transactions. This will
 | |
|     update the visible document and selection to match the state
 | |
|     produced by the transactions, and notify view plugins of the
 | |
|     change. You should usually call
 | |
|     [`dispatch`](https://codemirror.net/6/docs/ref/#view.EditorView.dispatch) instead, which uses this
 | |
|     as a primitive.
 | |
|     */
 | |
|     update(transactions) {
 | |
|         if (this.updateState != 0 /* UpdateState.Idle */)
 | |
|             throw new Error("Calls to EditorView.update are not allowed while an update is in progress");
 | |
|         let redrawn = false, attrsChanged = false, update;
 | |
|         let state = this.state;
 | |
|         for (let tr of transactions) {
 | |
|             if (tr.startState != state)
 | |
|                 throw new RangeError("Trying to update state with a transaction that doesn't start from the previous state.");
 | |
|             state = tr.state;
 | |
|         }
 | |
|         if (this.destroyed) {
 | |
|             this.viewState.state = state;
 | |
|             return;
 | |
|         }
 | |
|         let focus = this.hasFocus, focusFlag = 0, dispatchFocus = null;
 | |
|         if (transactions.some(tr => tr.annotation(isFocusChange))) {
 | |
|             this.inputState.notifiedFocused = focus;
 | |
|             // If a focus-change transaction is being dispatched, set this update flag.
 | |
|             focusFlag = 1 /* UpdateFlag.Focus */;
 | |
|         }
 | |
|         else if (focus != this.inputState.notifiedFocused) {
 | |
|             this.inputState.notifiedFocused = focus;
 | |
|             // Schedule a separate focus transaction if necessary, otherwise
 | |
|             // add a flag to this update
 | |
|             dispatchFocus = focusChangeTransaction(state, focus);
 | |
|             if (!dispatchFocus)
 | |
|                 focusFlag = 1 /* UpdateFlag.Focus */;
 | |
|         }
 | |
|         // If there was a pending DOM change, eagerly read it and try to
 | |
|         // apply it after the given transactions.
 | |
|         let pendingKey = this.observer.delayedAndroidKey, domChange = null;
 | |
|         if (pendingKey) {
 | |
|             this.observer.clearDelayedAndroidKey();
 | |
|             domChange = this.observer.readChange();
 | |
|             // Only try to apply DOM changes if the transactions didn't
 | |
|             // change the doc or selection.
 | |
|             if (domChange && !this.state.doc.eq(state.doc) || !this.state.selection.eq(state.selection))
 | |
|                 domChange = null;
 | |
|         }
 | |
|         else {
 | |
|             this.observer.clear();
 | |
|         }
 | |
|         // When the phrases change, redraw the editor
 | |
|         if (state.facet(EditorState.phrases) != this.state.facet(EditorState.phrases))
 | |
|             return this.setState(state);
 | |
|         update = ViewUpdate.create(this, state, transactions);
 | |
|         update.flags |= focusFlag;
 | |
|         let scrollTarget = this.viewState.scrollTarget;
 | |
|         try {
 | |
|             this.updateState = 2 /* UpdateState.Updating */;
 | |
|             for (let tr of transactions) {
 | |
|                 if (scrollTarget)
 | |
|                     scrollTarget = scrollTarget.map(tr.changes);
 | |
|                 if (tr.scrollIntoView) {
 | |
|                     let { main } = tr.state.selection;
 | |
|                     scrollTarget = new ScrollTarget(main.empty ? main : EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1));
 | |
|                 }
 | |
|                 for (let e of tr.effects)
 | |
|                     if (e.is(scrollIntoView))
 | |
|                         scrollTarget = e.value.clip(this.state);
 | |
|             }
 | |
|             this.viewState.update(update, scrollTarget);
 | |
|             this.bidiCache = CachedOrder.update(this.bidiCache, update.changes);
 | |
|             if (!update.empty) {
 | |
|                 this.updatePlugins(update);
 | |
|                 this.inputState.update(update);
 | |
|             }
 | |
|             redrawn = this.docView.update(update);
 | |
|             if (this.state.facet(styleModule) != this.styleModules)
 | |
|                 this.mountStyles();
 | |
|             attrsChanged = this.updateAttrs();
 | |
|             this.showAnnouncements(transactions);
 | |
|             this.docView.updateSelection(redrawn, transactions.some(tr => tr.isUserEvent("select.pointer")));
 | |
|         }
 | |
|         finally {
 | |
|             this.updateState = 0 /* UpdateState.Idle */;
 | |
|         }
 | |
|         if (update.startState.facet(theme) != update.state.facet(theme))
 | |
|             this.viewState.mustMeasureContent = true;
 | |
|         if (redrawn || attrsChanged || scrollTarget || this.viewState.mustEnforceCursorAssoc || this.viewState.mustMeasureContent)
 | |
|             this.requestMeasure();
 | |
|         if (redrawn)
 | |
|             this.docViewUpdate();
 | |
|         if (!update.empty)
 | |
|             for (let listener of this.state.facet(updateListener)) {
 | |
|                 try {
 | |
|                     listener(update);
 | |
|                 }
 | |
|                 catch (e) {
 | |
|                     logException(this.state, e, "update listener");
 | |
|                 }
 | |
|             }
 | |
|         if (dispatchFocus || domChange)
 | |
|             Promise.resolve().then(() => {
 | |
|                 if (dispatchFocus && this.state == dispatchFocus.startState)
 | |
|                     this.dispatch(dispatchFocus);
 | |
|                 if (domChange) {
 | |
|                     if (!applyDOMChange(this, domChange) && pendingKey.force)
 | |
|                         dispatchKey(this.contentDOM, pendingKey.key, pendingKey.keyCode);
 | |
|                 }
 | |
|             });
 | |
|     }
 | |
|     /**
 | |
|     Reset the view to the given state. (This will cause the entire
 | |
|     document to be redrawn and all view plugins to be reinitialized,
 | |
|     so you should probably only use it when the new state isn't
 | |
|     derived from the old state. Otherwise, use
 | |
|     [`dispatch`](https://codemirror.net/6/docs/ref/#view.EditorView.dispatch) instead.)
 | |
|     */
 | |
|     setState(newState) {
 | |
|         if (this.updateState != 0 /* UpdateState.Idle */)
 | |
|             throw new Error("Calls to EditorView.setState are not allowed while an update is in progress");
 | |
|         if (this.destroyed) {
 | |
|             this.viewState.state = newState;
 | |
|             return;
 | |
|         }
 | |
|         this.updateState = 2 /* UpdateState.Updating */;
 | |
|         let hadFocus = this.hasFocus;
 | |
|         try {
 | |
|             for (let plugin of this.plugins)
 | |
|                 plugin.destroy(this);
 | |
|             this.viewState = new ViewState(newState);
 | |
|             this.plugins = newState.facet(viewPlugin).map(spec => new PluginInstance(spec));
 | |
|             this.pluginMap.clear();
 | |
|             for (let plugin of this.plugins)
 | |
|                 plugin.update(this);
 | |
|             this.docView.destroy();
 | |
|             this.docView = new DocView(this);
 | |
|             this.inputState.ensureHandlers(this.plugins);
 | |
|             this.mountStyles();
 | |
|             this.updateAttrs();
 | |
|             this.bidiCache = [];
 | |
|         }
 | |
|         finally {
 | |
|             this.updateState = 0 /* UpdateState.Idle */;
 | |
|         }
 | |
|         if (hadFocus)
 | |
|             this.focus();
 | |
|         this.requestMeasure();
 | |
|     }
 | |
|     updatePlugins(update) {
 | |
|         let prevSpecs = update.startState.facet(viewPlugin), specs = update.state.facet(viewPlugin);
 | |
|         if (prevSpecs != specs) {
 | |
|             let newPlugins = [];
 | |
|             for (let spec of specs) {
 | |
|                 let found = prevSpecs.indexOf(spec);
 | |
|                 if (found < 0) {
 | |
|                     newPlugins.push(new PluginInstance(spec));
 | |
|                 }
 | |
|                 else {
 | |
|                     let plugin = this.plugins[found];
 | |
|                     plugin.mustUpdate = update;
 | |
|                     newPlugins.push(plugin);
 | |
|                 }
 | |
|             }
 | |
|             for (let plugin of this.plugins)
 | |
|                 if (plugin.mustUpdate != update)
 | |
|                     plugin.destroy(this);
 | |
|             this.plugins = newPlugins;
 | |
|             this.pluginMap.clear();
 | |
|         }
 | |
|         else {
 | |
|             for (let p of this.plugins)
 | |
|                 p.mustUpdate = update;
 | |
|         }
 | |
|         for (let i = 0; i < this.plugins.length; i++)
 | |
|             this.plugins[i].update(this);
 | |
|         if (prevSpecs != specs)
 | |
|             this.inputState.ensureHandlers(this.plugins);
 | |
|     }
 | |
|     docViewUpdate() {
 | |
|         for (let plugin of this.plugins) {
 | |
|             let val = plugin.value;
 | |
|             if (val && val.docViewUpdate) {
 | |
|                 try {
 | |
|                     val.docViewUpdate(this);
 | |
|                 }
 | |
|                 catch (e) {
 | |
|                     logException(this.state, e, "doc view update listener");
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     measure(flush = true) {
 | |
|         if (this.destroyed)
 | |
|             return;
 | |
|         if (this.measureScheduled > -1)
 | |
|             this.win.cancelAnimationFrame(this.measureScheduled);
 | |
|         if (this.observer.delayedAndroidKey) {
 | |
|             this.measureScheduled = -1;
 | |
|             this.requestMeasure();
 | |
|             return;
 | |
|         }
 | |
|         this.measureScheduled = 0; // Prevent requestMeasure calls from scheduling another animation frame
 | |
|         if (flush)
 | |
|             this.observer.forceFlush();
 | |
|         let updated = null;
 | |
|         let sDOM = this.scrollDOM, scrollTop = sDOM.scrollTop * this.scaleY;
 | |
|         let { scrollAnchorPos, scrollAnchorHeight } = this.viewState;
 | |
|         if (Math.abs(scrollTop - this.viewState.scrollTop) > 1)
 | |
|             scrollAnchorHeight = -1;
 | |
|         this.viewState.scrollAnchorHeight = -1;
 | |
|         try {
 | |
|             for (let i = 0;; i++) {
 | |
|                 if (scrollAnchorHeight < 0) {
 | |
|                     if (isScrolledToBottom(sDOM)) {
 | |
|                         scrollAnchorPos = -1;
 | |
|                         scrollAnchorHeight = this.viewState.heightMap.height;
 | |
|                     }
 | |
|                     else {
 | |
|                         let block = this.viewState.scrollAnchorAt(scrollTop);
 | |
|                         scrollAnchorPos = block.from;
 | |
|                         scrollAnchorHeight = block.top;
 | |
|                     }
 | |
|                 }
 | |
|                 this.updateState = 1 /* UpdateState.Measuring */;
 | |
|                 let changed = this.viewState.measure(this);
 | |
|                 if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
 | |
|                     break;
 | |
|                 if (i > 5) {
 | |
|                     console.warn(this.measureRequests.length
 | |
|                         ? "Measure loop restarted more than 5 times"
 | |
|                         : "Viewport failed to stabilize");
 | |
|                     break;
 | |
|                 }
 | |
|                 let measuring = [];
 | |
|                 // Only run measure requests in this cycle when the viewport didn't change
 | |
|                 if (!(changed & 4 /* UpdateFlag.Viewport */))
 | |
|                     [this.measureRequests, measuring] = [measuring, this.measureRequests];
 | |
|                 let measured = measuring.map(m => {
 | |
|                     try {
 | |
|                         return m.read(this);
 | |
|                     }
 | |
|                     catch (e) {
 | |
|                         logException(this.state, e);
 | |
|                         return BadMeasure;
 | |
|                     }
 | |
|                 });
 | |
|                 let update = ViewUpdate.create(this, this.state, []), redrawn = false;
 | |
|                 update.flags |= changed;
 | |
|                 if (!updated)
 | |
|                     updated = update;
 | |
|                 else
 | |
|                     updated.flags |= changed;
 | |
|                 this.updateState = 2 /* UpdateState.Updating */;
 | |
|                 if (!update.empty) {
 | |
|                     this.updatePlugins(update);
 | |
|                     this.inputState.update(update);
 | |
|                     this.updateAttrs();
 | |
|                     redrawn = this.docView.update(update);
 | |
|                     if (redrawn)
 | |
|                         this.docViewUpdate();
 | |
|                 }
 | |
|                 for (let i = 0; i < measuring.length; i++)
 | |
|                     if (measured[i] != BadMeasure) {
 | |
|                         try {
 | |
|                             let m = measuring[i];
 | |
|                             if (m.write)
 | |
|                                 m.write(measured[i], this);
 | |
|                         }
 | |
|                         catch (e) {
 | |
|                             logException(this.state, e);
 | |
|                         }
 | |
|                     }
 | |
|                 if (redrawn)
 | |
|                     this.docView.updateSelection(true);
 | |
|                 if (!update.viewportChanged && this.measureRequests.length == 0) {
 | |
|                     if (this.viewState.editorHeight) {
 | |
|                         if (this.viewState.scrollTarget) {
 | |
|                             this.docView.scrollIntoView(this.viewState.scrollTarget);
 | |
|                             this.viewState.scrollTarget = null;
 | |
|                             scrollAnchorHeight = -1;
 | |
|                             continue;
 | |
|                         }
 | |
|                         else {
 | |
|                             let newAnchorHeight = scrollAnchorPos < 0 ? this.viewState.heightMap.height :
 | |
|                                 this.viewState.lineBlockAt(scrollAnchorPos).top;
 | |
|                             let diff = newAnchorHeight - scrollAnchorHeight;
 | |
|                             if (diff > 1 || diff < -1) {
 | |
|                                 scrollTop = scrollTop + diff;
 | |
|                                 sDOM.scrollTop = scrollTop / this.scaleY;
 | |
|                                 scrollAnchorHeight = -1;
 | |
|                                 continue;
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         finally {
 | |
|             this.updateState = 0 /* UpdateState.Idle */;
 | |
|             this.measureScheduled = -1;
 | |
|         }
 | |
|         if (updated && !updated.empty)
 | |
|             for (let listener of this.state.facet(updateListener))
 | |
|                 listener(updated);
 | |
|     }
 | |
|     /**
 | |
|     Get the CSS classes for the currently active editor themes.
 | |
|     */
 | |
|     get themeClasses() {
 | |
|         return baseThemeID + " " +
 | |
|             (this.state.facet(darkTheme) ? baseDarkID : baseLightID) + " " +
 | |
|             this.state.facet(theme);
 | |
|     }
 | |
|     updateAttrs() {
 | |
|         let editorAttrs = attrsFromFacet(this, editorAttributes, {
 | |
|             class: "cm-editor" + (this.hasFocus ? " cm-focused " : " ") + this.themeClasses
 | |
|         });
 | |
|         let contentAttrs = {
 | |
|             spellcheck: "false",
 | |
|             autocorrect: "off",
 | |
|             autocapitalize: "off",
 | |
|             translate: "no",
 | |
|             contenteditable: !this.state.facet(editable) ? "false" : "true",
 | |
|             class: "cm-content",
 | |
|             style: `${browser.tabSize}: ${this.state.tabSize}`,
 | |
|             role: "textbox",
 | |
|             "aria-multiline": "true"
 | |
|         };
 | |
|         if (this.state.readOnly)
 | |
|             contentAttrs["aria-readonly"] = "true";
 | |
|         attrsFromFacet(this, contentAttributes, contentAttrs);
 | |
|         let changed = this.observer.ignore(() => {
 | |
|             let changedContent = updateAttrs(this.contentDOM, this.contentAttrs, contentAttrs);
 | |
|             let changedEditor = updateAttrs(this.dom, this.editorAttrs, editorAttrs);
 | |
|             return changedContent || changedEditor;
 | |
|         });
 | |
|         this.editorAttrs = editorAttrs;
 | |
|         this.contentAttrs = contentAttrs;
 | |
|         return changed;
 | |
|     }
 | |
|     showAnnouncements(trs) {
 | |
|         let first = true;
 | |
|         for (let tr of trs)
 | |
|             for (let effect of tr.effects)
 | |
|                 if (effect.is(EditorView.announce)) {
 | |
|                     if (first)
 | |
|                         this.announceDOM.textContent = "";
 | |
|                     first = false;
 | |
|                     let div = this.announceDOM.appendChild(document.createElement("div"));
 | |
|                     div.textContent = effect.value;
 | |
|                 }
 | |
|     }
 | |
|     mountStyles() {
 | |
|         this.styleModules = this.state.facet(styleModule);
 | |
|         let nonce = this.state.facet(EditorView.cspNonce);
 | |
|         StyleModule.mount(this.root, this.styleModules.concat(baseTheme$1).reverse(), nonce ? { nonce } : undefined);
 | |
|     }
 | |
|     readMeasured() {
 | |
|         if (this.updateState == 2 /* UpdateState.Updating */)
 | |
|             throw new Error("Reading the editor layout isn't allowed during an update");
 | |
|         if (this.updateState == 0 /* UpdateState.Idle */ && this.measureScheduled > -1)
 | |
|             this.measure(false);
 | |
|     }
 | |
|     /**
 | |
|     Schedule a layout measurement, optionally providing callbacks to
 | |
|     do custom DOM measuring followed by a DOM write phase. Using
 | |
|     this is preferable reading DOM layout directly from, for
 | |
|     example, an event handler, because it'll make sure measuring and
 | |
|     drawing done by other components is synchronized, avoiding
 | |
|     unnecessary DOM layout computations.
 | |
|     */
 | |
|     requestMeasure(request) {
 | |
|         if (this.measureScheduled < 0)
 | |
|             this.measureScheduled = this.win.requestAnimationFrame(() => this.measure());
 | |
|         if (request) {
 | |
|             if (this.measureRequests.indexOf(request) > -1)
 | |
|                 return;
 | |
|             if (request.key != null)
 | |
|                 for (let i = 0; i < this.measureRequests.length; i++) {
 | |
|                     if (this.measureRequests[i].key === request.key) {
 | |
|                         this.measureRequests[i] = request;
 | |
|                         return;
 | |
|                     }
 | |
|                 }
 | |
|             this.measureRequests.push(request);
 | |
|         }
 | |
|     }
 | |
|     /**
 | |
|     Get the value of a specific plugin, if present. Note that
 | |
|     plugins that crash can be dropped from a view, so even when you
 | |
|     know you registered a given plugin, it is recommended to check
 | |
|     the return value of this method.
 | |
|     */
 | |
|     plugin(plugin) {
 | |
|         let known = this.pluginMap.get(plugin);
 | |
|         if (known === undefined || known && known.spec != plugin)
 | |
|             this.pluginMap.set(plugin, known = this.plugins.find(p => p.spec == plugin) || null);
 | |
|         return known && known.update(this).value;
 | |
|     }
 | |
|     /**
 | |
|     The top position of the document, in screen coordinates. This
 | |
|     may be negative when the editor is scrolled down. Points
 | |
|     directly to the top of the first line, not above the padding.
 | |
|     */
 | |
|     get documentTop() {
 | |
|         return this.contentDOM.getBoundingClientRect().top + this.viewState.paddingTop;
 | |
|     }
 | |
|     /**
 | |
|     Reports the padding above and below the document.
 | |
|     */
 | |
|     get documentPadding() {
 | |
|         return { top: this.viewState.paddingTop, bottom: this.viewState.paddingBottom };
 | |
|     }
 | |
|     /**
 | |
|     If the editor is transformed with CSS, this provides the scale
 | |
|     along the X axis. Otherwise, it will just be 1. Note that
 | |
|     transforms other than translation and scaling are not supported.
 | |
|     */
 | |
|     get scaleX() { return this.viewState.scaleX; }
 | |
|     /**
 | |
|     Provide the CSS transformed scale along the Y axis.
 | |
|     */
 | |
|     get scaleY() { return this.viewState.scaleY; }
 | |
|     /**
 | |
|     Find the text line or block widget at the given vertical
 | |
|     position (which is interpreted as relative to the [top of the
 | |
|     document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop)).
 | |
|     */
 | |
|     elementAtHeight(height) {
 | |
|         this.readMeasured();
 | |
|         return this.viewState.elementAtHeight(height);
 | |
|     }
 | |
|     /**
 | |
|     Find the line block (see
 | |
|     [`lineBlockAt`](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt) at the given
 | |
|     height, again interpreted relative to the [top of the
 | |
|     document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop).
 | |
|     */
 | |
|     lineBlockAtHeight(height) {
 | |
|         this.readMeasured();
 | |
|         return this.viewState.lineBlockAtHeight(height);
 | |
|     }
 | |
|     /**
 | |
|     Get the extent and vertical position of all [line
 | |
|     blocks](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt) in the viewport. Positions
 | |
|     are relative to the [top of the
 | |
|     document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop);
 | |
|     */
 | |
|     get viewportLineBlocks() {
 | |
|         return this.viewState.viewportLines;
 | |
|     }
 | |
|     /**
 | |
|     Find the line block around the given document position. A line
 | |
|     block is a range delimited on both sides by either a
 | |
|     non-[hidden](https://codemirror.net/6/docs/ref/#view.Decoration^replace) line break, or the
 | |
|     start/end of the document. It will usually just hold a line of
 | |
|     text, but may be broken into multiple textblocks by block
 | |
|     widgets.
 | |
|     */
 | |
|     lineBlockAt(pos) {
 | |
|         return this.viewState.lineBlockAt(pos);
 | |
|     }
 | |
|     /**
 | |
|     The editor's total content height.
 | |
|     */
 | |
|     get contentHeight() {
 | |
|         return this.viewState.contentHeight;
 | |
|     }
 | |
|     /**
 | |
|     Move a cursor position by [grapheme
 | |
|     cluster](https://codemirror.net/6/docs/ref/#state.findClusterBreak). `forward` determines whether
 | |
|     the motion is away from the line start, or towards it. In
 | |
|     bidirectional text, the line is traversed in visual order, using
 | |
|     the editor's [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
 | |
|     When the start position was the last one on the line, the
 | |
|     returned position will be across the line break. If there is no
 | |
|     further line, the original position is returned.
 | |
|     
 | |
|     By default, this method moves over a single cluster. The
 | |
|     optional `by` argument can be used to move across more. It will
 | |
|     be called with the first cluster as argument, and should return
 | |
|     a predicate that determines, for each subsequent cluster,
 | |
|     whether it should also be moved over.
 | |
|     */
 | |
|     moveByChar(start, forward, by) {
 | |
|         return skipAtoms(this, start, moveByChar(this, start, forward, by));
 | |
|     }
 | |
|     /**
 | |
|     Move a cursor position across the next group of either
 | |
|     [letters](https://codemirror.net/6/docs/ref/#state.EditorState.charCategorizer) or non-letter
 | |
|     non-whitespace characters.
 | |
|     */
 | |
|     moveByGroup(start, forward) {
 | |
|         return skipAtoms(this, start, moveByChar(this, start, forward, initial => byGroup(this, start.head, initial)));
 | |
|     }
 | |
|     /**
 | |
|     Get the cursor position visually at the start or end of a line.
 | |
|     Note that this may differ from the _logical_ position at its
 | |
|     start or end (which is simply at `line.from`/`line.to`) if text
 | |
|     at the start or end goes against the line's base text direction.
 | |
|     */
 | |
|     visualLineSide(line, end) {
 | |
|         let order = this.bidiSpans(line), dir = this.textDirectionAt(line.from);
 | |
|         let span = order[end ? order.length - 1 : 0];
 | |
|         return EditorSelection.cursor(span.side(end, dir) + line.from, span.forward(!end, dir) ? 1 : -1);
 | |
|     }
 | |
|     /**
 | |
|     Move to the next line boundary in the given direction. If
 | |
|     `includeWrap` is true, line wrapping is on, and there is a
 | |
|     further wrap point on the current line, the wrap point will be
 | |
|     returned. Otherwise this function will return the start or end
 | |
|     of the line.
 | |
|     */
 | |
|     moveToLineBoundary(start, forward, includeWrap = true) {
 | |
|         return moveToLineBoundary(this, start, forward, includeWrap);
 | |
|     }
 | |
|     /**
 | |
|     Move a cursor position vertically. When `distance` isn't given,
 | |
|     it defaults to moving to the next line (including wrapped
 | |
|     lines). Otherwise, `distance` should provide a positive distance
 | |
|     in pixels.
 | |
|     
 | |
|     When `start` has a
 | |
|     [`goalColumn`](https://codemirror.net/6/docs/ref/#state.SelectionRange.goalColumn), the vertical
 | |
|     motion will use that as a target horizontal position. Otherwise,
 | |
|     the cursor's own horizontal position is used. The returned
 | |
|     cursor will have its goal column set to whichever column was
 | |
|     used.
 | |
|     */
 | |
|     moveVertically(start, forward, distance) {
 | |
|         return skipAtoms(this, start, moveVertically(this, start, forward, distance));
 | |
|     }
 | |
|     /**
 | |
|     Find the DOM parent node and offset (child offset if `node` is
 | |
|     an element, character offset when it is a text node) at the
 | |
|     given document position.
 | |
|     
 | |
|     Note that for positions that aren't currently in
 | |
|     `visibleRanges`, the resulting DOM position isn't necessarily
 | |
|     meaningful (it may just point before or after a placeholder
 | |
|     element).
 | |
|     */
 | |
|     domAtPos(pos) {
 | |
|         return this.docView.domAtPos(pos);
 | |
|     }
 | |
|     /**
 | |
|     Find the document position at the given DOM node. Can be useful
 | |
|     for associating positions with DOM events. Will raise an error
 | |
|     when `node` isn't part of the editor content.
 | |
|     */
 | |
|     posAtDOM(node, offset = 0) {
 | |
|         return this.docView.posFromDOM(node, offset);
 | |
|     }
 | |
|     posAtCoords(coords, precise = true) {
 | |
|         this.readMeasured();
 | |
|         return posAtCoords(this, coords, precise);
 | |
|     }
 | |
|     /**
 | |
|     Get the screen coordinates at the given document position.
 | |
|     `side` determines whether the coordinates are based on the
 | |
|     element before (-1) or after (1) the position (if no element is
 | |
|     available on the given side, the method will transparently use
 | |
|     another strategy to get reasonable coordinates).
 | |
|     */
 | |
|     coordsAtPos(pos, side = 1) {
 | |
|         this.readMeasured();
 | |
|         let rect = this.docView.coordsAt(pos, side);
 | |
|         if (!rect || rect.left == rect.right)
 | |
|             return rect;
 | |
|         let line = this.state.doc.lineAt(pos), order = this.bidiSpans(line);
 | |
|         let span = order[BidiSpan.find(order, pos - line.from, -1, side)];
 | |
|         return flattenRect(rect, (span.dir == Direction.LTR) == (side > 0));
 | |
|     }
 | |
|     /**
 | |
|     Return the rectangle around a given character. If `pos` does not
 | |
|     point in front of a character that is in the viewport and
 | |
|     rendered (i.e. not replaced, not a line break), this will return
 | |
|     null. For space characters that are a line wrap point, this will
 | |
|     return the position before the line break.
 | |
|     */
 | |
|     coordsForChar(pos) {
 | |
|         this.readMeasured();
 | |
|         return this.docView.coordsForChar(pos);
 | |
|     }
 | |
|     /**
 | |
|     The default width of a character in the editor. May not
 | |
|     accurately reflect the width of all characters (given variable
 | |
|     width fonts or styling of invididual ranges).
 | |
|     */
 | |
|     get defaultCharacterWidth() { return this.viewState.heightOracle.charWidth; }
 | |
|     /**
 | |
|     The default height of a line in the editor. May not be accurate
 | |
|     for all lines.
 | |
|     */
 | |
|     get defaultLineHeight() { return this.viewState.heightOracle.lineHeight; }
 | |
|     /**
 | |
|     The text direction
 | |
|     ([`direction`](https://developer.mozilla.org/en-US/docs/Web/CSS/direction)
 | |
|     CSS property) of the editor's content element.
 | |
|     */
 | |
|     get textDirection() { return this.viewState.defaultTextDirection; }
 | |
|     /**
 | |
|     Find the text direction of the block at the given position, as
 | |
|     assigned by CSS. If
 | |
|     [`perLineTextDirection`](https://codemirror.net/6/docs/ref/#view.EditorView^perLineTextDirection)
 | |
|     isn't enabled, or the given position is outside of the viewport,
 | |
|     this will always return the same as
 | |
|     [`textDirection`](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection). Note that
 | |
|     this may trigger a DOM layout.
 | |
|     */
 | |
|     textDirectionAt(pos) {
 | |
|         let perLine = this.state.facet(perLineTextDirection);
 | |
|         if (!perLine || pos < this.viewport.from || pos > this.viewport.to)
 | |
|             return this.textDirection;
 | |
|         this.readMeasured();
 | |
|         return this.docView.textDirectionAt(pos);
 | |
|     }
 | |
|     /**
 | |
|     Whether this editor [wraps lines](https://codemirror.net/6/docs/ref/#view.EditorView.lineWrapping)
 | |
|     (as determined by the
 | |
|     [`white-space`](https://developer.mozilla.org/en-US/docs/Web/CSS/white-space)
 | |
|     CSS property of its content element).
 | |
|     */
 | |
|     get lineWrapping() { return this.viewState.heightOracle.lineWrapping; }
 | |
|     /**
 | |
|     Returns the bidirectional text structure of the given line
 | |
|     (which should be in the current document) as an array of span
 | |
|     objects. The order of these spans matches the [text
 | |
|     direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection)—if that is
 | |
|     left-to-right, the leftmost spans come first, otherwise the
 | |
|     rightmost spans come first.
 | |
|     */
 | |
|     bidiSpans(line) {
 | |
|         if (line.length > MaxBidiLine)
 | |
|             return trivialOrder(line.length);
 | |
|         let dir = this.textDirectionAt(line.from), isolates;
 | |
|         for (let entry of this.bidiCache) {
 | |
|             if (entry.from == line.from && entry.dir == dir &&
 | |
|                 (entry.fresh || isolatesEq(entry.isolates, isolates = getIsolatedRanges(this, line))))
 | |
|                 return entry.order;
 | |
|         }
 | |
|         if (!isolates)
 | |
|             isolates = getIsolatedRanges(this, line);
 | |
|         let order = computeOrder(line.text, dir, isolates);
 | |
|         this.bidiCache.push(new CachedOrder(line.from, line.to, dir, isolates, true, order));
 | |
|         return order;
 | |
|     }
 | |
|     /**
 | |
|     Check whether the editor has focus.
 | |
|     */
 | |
|     get hasFocus() {
 | |
|         var _a;
 | |
|         // Safari return false for hasFocus when the context menu is open
 | |
|         // or closing, which leads us to ignore selection changes from the
 | |
|         // context menu because it looks like the editor isn't focused.
 | |
|         // This kludges around that.
 | |
|         return (this.dom.ownerDocument.hasFocus() || browser.safari && ((_a = this.inputState) === null || _a === void 0 ? void 0 : _a.lastContextMenu) > Date.now() - 3e4) &&
 | |
|             this.root.activeElement == this.contentDOM;
 | |
|     }
 | |
|     /**
 | |
|     Put focus on the editor.
 | |
|     */
 | |
|     focus() {
 | |
|         this.observer.ignore(() => {
 | |
|             focusPreventScroll(this.contentDOM);
 | |
|             this.docView.updateSelection();
 | |
|         });
 | |
|     }
 | |
|     /**
 | |
|     Update the [root](https://codemirror.net/6/docs/ref/##view.EditorViewConfig.root) in which the editor lives. This is only
 | |
|     necessary when moving the editor's existing DOM to a new window or shadow root.
 | |
|     */
 | |
|     setRoot(root) {
 | |
|         if (this._root != root) {
 | |
|             this._root = root;
 | |
|             this.observer.setWindow((root.nodeType == 9 ? root : root.ownerDocument).defaultView || window);
 | |
|             this.mountStyles();
 | |
|         }
 | |
|     }
 | |
|     /**
 | |
|     Clean up this editor view, removing its element from the
 | |
|     document, unregistering event handlers, and notifying
 | |
|     plugins. The view instance can no longer be used after
 | |
|     calling this.
 | |
|     */
 | |
|     destroy() {
 | |
|         if (this.root.activeElement == this.contentDOM)
 | |
|             this.contentDOM.blur();
 | |
|         for (let plugin of this.plugins)
 | |
|             plugin.destroy(this);
 | |
|         this.plugins = [];
 | |
|         this.inputState.destroy();
 | |
|         this.docView.destroy();
 | |
|         this.dom.remove();
 | |
|         this.observer.destroy();
 | |
|         if (this.measureScheduled > -1)
 | |
|             this.win.cancelAnimationFrame(this.measureScheduled);
 | |
|         this.destroyed = true;
 | |
|     }
 | |
|     /**
 | |
|     Returns an effect that can be
 | |
|     [added](https://codemirror.net/6/docs/ref/#state.TransactionSpec.effects) to a transaction to
 | |
|     cause it to scroll the given position or range into view.
 | |
|     */
 | |
|     static scrollIntoView(pos, options = {}) {
 | |
|         return scrollIntoView.of(new ScrollTarget(typeof pos == "number" ? EditorSelection.cursor(pos) : pos, options.y, options.x, options.yMargin, options.xMargin));
 | |
|     }
 | |
|     /**
 | |
|     Return an effect that resets the editor to its current (at the
 | |
|     time this method was called) scroll position. Note that this
 | |
|     only affects the editor's own scrollable element, not parents.
 | |
|     See also
 | |
|     [`EditorViewConfig.scrollTo`](https://codemirror.net/6/docs/ref/#view.EditorViewConfig.scrollTo).
 | |
|     
 | |
|     The effect should be used with a document identical to the one
 | |
|     it was created for. Failing to do so is not an error, but may
 | |
|     not scroll to the expected position. You can
 | |
|     [map](https://codemirror.net/6/docs/ref/#state.StateEffect.map) the effect to account for changes.
 | |
|     */
 | |
|     scrollSnapshot() {
 | |
|         let { scrollTop, scrollLeft } = this.scrollDOM;
 | |
|         let ref = this.viewState.scrollAnchorAt(scrollTop);
 | |
|         return scrollIntoView.of(new ScrollTarget(EditorSelection.cursor(ref.from), "start", "start", ref.top - scrollTop, scrollLeft, true));
 | |
|     }
 | |
|     /**
 | |
|     Enable or disable tab-focus mode, which disables key bindings
 | |
|     for Tab and Shift-Tab, letting the browser's default
 | |
|     focus-changing behavior go through instead. This is useful to
 | |
|     prevent trapping keyboard users in your editor.
 | |
|     
 | |
|     Without argument, this toggles the mode. With a boolean, it
 | |
|     enables (true) or disables it (false). Given a number, it
 | |
|     temporarily enables the mode until that number of milliseconds
 | |
|     have passed or another non-Tab key is pressed.
 | |
|     */
 | |
|     setTabFocusMode(to) {
 | |
|         if (to == null)
 | |
|             this.inputState.tabFocusMode = this.inputState.tabFocusMode < 0 ? 0 : -1;
 | |
|         else if (typeof to == "boolean")
 | |
|             this.inputState.tabFocusMode = to ? 0 : -1;
 | |
|         else if (this.inputState.tabFocusMode != 0)
 | |
|             this.inputState.tabFocusMode = Date.now() + to;
 | |
|     }
 | |
|     /**
 | |
|     Returns an extension that can be used to add DOM event handlers.
 | |
|     The value should be an object mapping event names to handler
 | |
|     functions. For any given event, such functions are ordered by
 | |
|     extension precedence, and the first handler to return true will
 | |
|     be assumed to have handled that event, and no other handlers or
 | |
|     built-in behavior will be activated for it. These are registered
 | |
|     on the [content element](https://codemirror.net/6/docs/ref/#view.EditorView.contentDOM), except
 | |
|     for `scroll` handlers, which will be called any time the
 | |
|     editor's [scroll element](https://codemirror.net/6/docs/ref/#view.EditorView.scrollDOM) or one of
 | |
|     its parent nodes is scrolled.
 | |
|     */
 | |
|     static domEventHandlers(handlers) {
 | |
|         return ViewPlugin.define(() => ({}), { eventHandlers: handlers });
 | |
|     }
 | |
|     /**
 | |
|     Create an extension that registers DOM event observers. Contrary
 | |
|     to event [handlers](https://codemirror.net/6/docs/ref/#view.EditorView^domEventHandlers),
 | |
|     observers can't be prevented from running by a higher-precedence
 | |
|     handler returning true. They also don't prevent other handlers
 | |
|     and observers from running when they return true, and should not
 | |
|     call `preventDefault`.
 | |
|     */
 | |
|     static domEventObservers(observers) {
 | |
|         return ViewPlugin.define(() => ({}), { eventObservers: observers });
 | |
|     }
 | |
|     /**
 | |
|     Create a theme extension. The first argument can be a
 | |
|     [`style-mod`](https://github.com/marijnh/style-mod#documentation)
 | |
|     style spec providing the styles for the theme. These will be
 | |
|     prefixed with a generated class for the style.
 | |
|     
 | |
|     Because the selectors will be prefixed with a scope class, rule
 | |
|     that directly match the editor's [wrapper
 | |
|     element](https://codemirror.net/6/docs/ref/#view.EditorView.dom)—to which the scope class will be
 | |
|     added—need to be explicitly differentiated by adding an `&` to
 | |
|     the selector for that element—for example
 | |
|     `&.cm-focused`.
 | |
|     
 | |
|     When `dark` is set to true, the theme will be marked as dark,
 | |
|     which will cause the `&dark` rules from [base
 | |
|     themes](https://codemirror.net/6/docs/ref/#view.EditorView^baseTheme) to be used (as opposed to
 | |
|     `&light` when a light theme is active).
 | |
|     */
 | |
|     static theme(spec, options) {
 | |
|         let prefix = StyleModule.newName();
 | |
|         let result = [theme.of(prefix), styleModule.of(buildTheme(`.${prefix}`, spec))];
 | |
|         if (options && options.dark)
 | |
|             result.push(darkTheme.of(true));
 | |
|         return result;
 | |
|     }
 | |
|     /**
 | |
|     Create an extension that adds styles to the base theme. Like
 | |
|     with [`theme`](https://codemirror.net/6/docs/ref/#view.EditorView^theme), use `&` to indicate the
 | |
|     place of the editor wrapper element when directly targeting
 | |
|     that. You can also use `&dark` or `&light` instead to only
 | |
|     target editors with a dark or light theme.
 | |
|     */
 | |
|     static baseTheme(spec) {
 | |
|         return Prec.lowest(styleModule.of(buildTheme("." + baseThemeID, spec, lightDarkIDs)));
 | |
|     }
 | |
|     /**
 | |
|     Retrieve an editor view instance from the view's DOM
 | |
|     representation.
 | |
|     */
 | |
|     static findFromDOM(dom) {
 | |
|         var _a;
 | |
|         let content = dom.querySelector(".cm-content");
 | |
|         let cView = content && ContentView.get(content) || ContentView.get(dom);
 | |
|         return ((_a = cView === null || cView === void 0 ? void 0 : cView.rootView) === null || _a === void 0 ? void 0 : _a.view) || null;
 | |
|     }
 | |
| }
 | |
| /**
 | |
| Facet to add a [style
 | |
| module](https://github.com/marijnh/style-mod#documentation) to
 | |
| an editor view. The view will ensure that the module is
 | |
| mounted in its [document
 | |
| root](https://codemirror.net/6/docs/ref/#view.EditorView.constructor^config.root).
 | |
| */
 | |
| EditorView.styleModule = styleModule;
 | |
| /**
 | |
| An input handler can override the way changes to the editable
 | |
| DOM content are handled. Handlers are passed the document
 | |
| positions between which the change was found, and the new
 | |
| content. When one returns true, no further input handlers are
 | |
| called and the default behavior is prevented.
 | |
| 
 | |
| The `insert` argument can be used to get the default transaction
 | |
| that would be applied for this input. This can be useful when
 | |
| dispatching the custom behavior as a separate transaction.
 | |
| */
 | |
| EditorView.inputHandler = inputHandler;
 | |
| /**
 | |
| Scroll handlers can override how things are scrolled into view.
 | |
| If they return `true`, no further handling happens for the
 | |
| scrolling. If they return false, the default scroll behavior is
 | |
| applied. Scroll handlers should never initiate editor updates.
 | |
| */
 | |
| EditorView.scrollHandler = scrollHandler;
 | |
| /**
 | |
| This facet can be used to provide functions that create effects
 | |
| to be dispatched when the editor's focus state changes.
 | |
| */
 | |
| EditorView.focusChangeEffect = focusChangeEffect;
 | |
| /**
 | |
| By default, the editor assumes all its content has the same
 | |
| [text direction](https://codemirror.net/6/docs/ref/#view.Direction). Configure this with a `true`
 | |
| value to make it read the text direction of every (rendered)
 | |
| line separately.
 | |
| */
 | |
| EditorView.perLineTextDirection = perLineTextDirection;
 | |
| /**
 | |
| Allows you to provide a function that should be called when the
 | |
| library catches an exception from an extension (mostly from view
 | |
| plugins, but may be used by other extensions to route exceptions
 | |
| from user-code-provided callbacks). This is mostly useful for
 | |
| debugging and logging. See [`logException`](https://codemirror.net/6/docs/ref/#view.logException).
 | |
| */
 | |
| EditorView.exceptionSink = exceptionSink;
 | |
| /**
 | |
| A facet that can be used to register a function to be called
 | |
| every time the view updates.
 | |
| */
 | |
| EditorView.updateListener = updateListener;
 | |
| /**
 | |
| Facet that controls whether the editor content DOM is editable.
 | |
| When its highest-precedence value is `false`, the element will
 | |
| not have its `contenteditable` attribute set. (Note that this
 | |
| doesn't affect API calls that change the editor content, even
 | |
| when those are bound to keys or buttons. See the
 | |
| [`readOnly`](https://codemirror.net/6/docs/ref/#state.EditorState.readOnly) facet for that.)
 | |
| */
 | |
| EditorView.editable = editable;
 | |
| /**
 | |
| Allows you to influence the way mouse selection happens. The
 | |
| functions in this facet will be called for a `mousedown` event
 | |
| on the editor, and can return an object that overrides the way a
 | |
| selection is computed from that mouse click or drag.
 | |
| */
 | |
| EditorView.mouseSelectionStyle = mouseSelectionStyle;
 | |
| /**
 | |
| Facet used to configure whether a given selection drag event
 | |
| should move or copy the selection. The given predicate will be
 | |
| called with the `mousedown` event, and can return `true` when
 | |
| the drag should move the content.
 | |
| */
 | |
| EditorView.dragMovesSelection = dragMovesSelection$1;
 | |
| /**
 | |
| Facet used to configure whether a given selecting click adds a
 | |
| new range to the existing selection or replaces it entirely. The
 | |
| default behavior is to check `event.metaKey` on macOS, and
 | |
| `event.ctrlKey` elsewhere.
 | |
| */
 | |
| EditorView.clickAddsSelectionRange = clickAddsSelectionRange;
 | |
| /**
 | |
| A facet that determines which [decorations](https://codemirror.net/6/docs/ref/#view.Decoration)
 | |
| are shown in the view. Decorations can be provided in two
 | |
| ways—directly, or via a function that takes an editor view.
 | |
| 
 | |
| Only decoration sets provided directly are allowed to influence
 | |
| the editor's vertical layout structure. The ones provided as
 | |
| functions are called _after_ the new viewport has been computed,
 | |
| and thus **must not** introduce block widgets or replacing
 | |
| decorations that cover line breaks.
 | |
| 
 | |
| If you want decorated ranges to behave like atomic units for
 | |
| cursor motion and deletion purposes, also provide the range set
 | |
| containing the decorations to
 | |
| [`EditorView.atomicRanges`](https://codemirror.net/6/docs/ref/#view.EditorView^atomicRanges).
 | |
| */
 | |
| EditorView.decorations = decorations;
 | |
| /**
 | |
| Facet that works much like
 | |
| [`decorations`](https://codemirror.net/6/docs/ref/#view.EditorView^decorations), but puts its
 | |
| inputs at the very bottom of the precedence stack, meaning mark
 | |
| decorations provided here will only be split by other, partially
 | |
| overlapping \`outerDecorations\` ranges, and wrap around all
 | |
| regular decorations. Use this for mark elements that should, as
 | |
| much as possible, remain in one piece.
 | |
| */
 | |
| EditorView.outerDecorations = outerDecorations;
 | |
| /**
 | |
| Used to provide ranges that should be treated as atoms as far as
 | |
| cursor motion is concerned. This causes methods like
 | |
| [`moveByChar`](https://codemirror.net/6/docs/ref/#view.EditorView.moveByChar) and
 | |
| [`moveVertically`](https://codemirror.net/6/docs/ref/#view.EditorView.moveVertically) (and the
 | |
| commands built on top of them) to skip across such regions when
 | |
| a selection endpoint would enter them. This does _not_ prevent
 | |
| direct programmatic [selection
 | |
| updates](https://codemirror.net/6/docs/ref/#state.TransactionSpec.selection) from moving into such
 | |
| regions.
 | |
| */
 | |
| EditorView.atomicRanges = atomicRanges;
 | |
| /**
 | |
| When range decorations add a `unicode-bidi: isolate` style, they
 | |
| should also include a
 | |
| [`bidiIsolate`](https://codemirror.net/6/docs/ref/#view.MarkDecorationSpec.bidiIsolate) property
 | |
| in their decoration spec, and be exposed through this facet, so
 | |
| that the editor can compute the proper text order. (Other values
 | |
| for `unicode-bidi`, except of course `normal`, are not
 | |
| supported.)
 | |
| */
 | |
| EditorView.bidiIsolatedRanges = bidiIsolatedRanges;
 | |
| /**
 | |
| Facet that allows extensions to provide additional scroll
 | |
| margins (space around the sides of the scrolling element that
 | |
| should be considered invisible). This can be useful when the
 | |
| plugin introduces elements that cover part of that element (for
 | |
| example a horizontally fixed gutter).
 | |
| */
 | |
| EditorView.scrollMargins = scrollMargins;
 | |
| /**
 | |
| This facet records whether a dark theme is active. The extension
 | |
| returned by [`theme`](https://codemirror.net/6/docs/ref/#view.EditorView^theme) automatically
 | |
| includes an instance of this when the `dark` option is set to
 | |
| true.
 | |
| */
 | |
| EditorView.darkTheme = darkTheme;
 | |
| /**
 | |
| Provides a Content Security Policy nonce to use when creating
 | |
| the style sheets for the editor. Holds the empty string when no
 | |
| nonce has been provided.
 | |
| */
 | |
| EditorView.cspNonce = /*@__PURE__*/Facet.define({ combine: values => values.length ? values[0] : "" });
 | |
| /**
 | |
| Facet that provides additional DOM attributes for the editor's
 | |
| editable DOM element.
 | |
| */
 | |
| EditorView.contentAttributes = contentAttributes;
 | |
| /**
 | |
| Facet that provides DOM attributes for the editor's outer
 | |
| element.
 | |
| */
 | |
| EditorView.editorAttributes = editorAttributes;
 | |
| /**
 | |
| An extension that enables line wrapping in the editor (by
 | |
| setting CSS `white-space` to `pre-wrap` in the content).
 | |
| */
 | |
| EditorView.lineWrapping = /*@__PURE__*/EditorView.contentAttributes.of({ "class": "cm-lineWrapping" });
 | |
| /**
 | |
| State effect used to include screen reader announcements in a
 | |
| transaction. These will be added to the DOM in a visually hidden
 | |
| element with `aria-live="polite"` set, and should be used to
 | |
| describe effects that are visually obvious but may not be
 | |
| noticed by screen reader users (such as moving to the next
 | |
| search match).
 | |
| */
 | |
| EditorView.announce = /*@__PURE__*/StateEffect.define();
 | |
| // Maximum line length for which we compute accurate bidi info
 | |
| const MaxBidiLine = 4096;
 | |
| const BadMeasure = {};
 | |
| class CachedOrder {
 | |
|     constructor(from, to, dir, isolates, fresh, order) {
 | |
|         this.from = from;
 | |
|         this.to = to;
 | |
|         this.dir = dir;
 | |
|         this.isolates = isolates;
 | |
|         this.fresh = fresh;
 | |
|         this.order = order;
 | |
|     }
 | |
|     static update(cache, changes) {
 | |
|         if (changes.empty && !cache.some(c => c.fresh))
 | |
|             return cache;
 | |
|         let result = [], lastDir = cache.length ? cache[cache.length - 1].dir : Direction.LTR;
 | |
|         for (let i = Math.max(0, cache.length - 10); i < cache.length; i++) {
 | |
|             let entry = cache[i];
 | |
|             if (entry.dir == lastDir && !changes.touchesRange(entry.from, entry.to))
 | |
|                 result.push(new CachedOrder(changes.mapPos(entry.from, 1), changes.mapPos(entry.to, -1), entry.dir, entry.isolates, false, entry.order));
 | |
|         }
 | |
|         return result;
 | |
|     }
 | |
| }
 | |
| function attrsFromFacet(view, facet, base) {
 | |
|     for (let sources = view.state.facet(facet), i = sources.length - 1; i >= 0; i--) {
 | |
|         let source = sources[i], value = typeof source == "function" ? source(view) : source;
 | |
|         if (value)
 | |
|             combineAttrs(value, base);
 | |
|     }
 | |
|     return base;
 | |
| }
 | |
| 
 | |
| const currentPlatform = browser.mac ? "mac" : browser.windows ? "win" : browser.linux ? "linux" : "key";
 | |
| function normalizeKeyName(name, platform) {
 | |
|     const parts = name.split(/-(?!$)/);
 | |
|     let result = parts[parts.length - 1];
 | |
|     if (result == "Space")
 | |
|         result = " ";
 | |
|     let alt, ctrl, shift, meta;
 | |
|     for (let i = 0; i < parts.length - 1; ++i) {
 | |
|         const mod = parts[i];
 | |
|         if (/^(cmd|meta|m)$/i.test(mod))
 | |
|             meta = true;
 | |
|         else if (/^a(lt)?$/i.test(mod))
 | |
|             alt = true;
 | |
|         else if (/^(c|ctrl|control)$/i.test(mod))
 | |
|             ctrl = true;
 | |
|         else if (/^s(hift)?$/i.test(mod))
 | |
|             shift = true;
 | |
|         else if (/^mod$/i.test(mod)) {
 | |
|             if (platform == "mac")
 | |
|                 meta = true;
 | |
|             else
 | |
|                 ctrl = true;
 | |
|         }
 | |
|         else
 | |
|             throw new Error("Unrecognized modifier name: " + mod);
 | |
|     }
 | |
|     if (alt)
 | |
|         result = "Alt-" + result;
 | |
|     if (ctrl)
 | |
|         result = "Ctrl-" + result;
 | |
|     if (meta)
 | |
|         result = "Meta-" + result;
 | |
|     if (shift)
 | |
|         result = "Shift-" + result;
 | |
|     return result;
 | |
| }
 | |
| function modifiers(name, event, shift) {
 | |
|     if (event.altKey)
 | |
|         name = "Alt-" + name;
 | |
|     if (event.ctrlKey)
 | |
|         name = "Ctrl-" + name;
 | |
|     if (event.metaKey)
 | |
|         name = "Meta-" + name;
 | |
|     if (shift !== false && event.shiftKey)
 | |
|         name = "Shift-" + name;
 | |
|     return name;
 | |
| }
 | |
| const handleKeyEvents = /*@__PURE__*/Prec.default(/*@__PURE__*/EditorView.domEventHandlers({
 | |
|     keydown(event, view) {
 | |
|         return runHandlers(getKeymap(view.state), event, view, "editor");
 | |
|     }
 | |
| }));
 | |
| /**
 | |
| Facet used for registering keymaps.
 | |
| 
 | |
| You can add multiple keymaps to an editor. Their priorities
 | |
| determine their precedence (the ones specified early or with high
 | |
| priority get checked first). When a handler has returned `true`
 | |
| for a given key, no further handlers are called.
 | |
| */
 | |
| const keymap = /*@__PURE__*/Facet.define({ enables: handleKeyEvents });
 | |
| const Keymaps = /*@__PURE__*/new WeakMap();
 | |
| // This is hidden behind an indirection, rather than directly computed
 | |
| // by the facet, to keep internal types out of the facet's type.
 | |
| function getKeymap(state) {
 | |
|     let bindings = state.facet(keymap);
 | |
|     let map = Keymaps.get(bindings);
 | |
|     if (!map)
 | |
|         Keymaps.set(bindings, map = buildKeymap(bindings.reduce((a, b) => a.concat(b), [])));
 | |
|     return map;
 | |
| }
 | |
| /**
 | |
| Run the key handlers registered for a given scope. The event
 | |
| object should be a `"keydown"` event. Returns true if any of the
 | |
| handlers handled it.
 | |
| */
 | |
| function runScopeHandlers(view, event, scope) {
 | |
|     return runHandlers(getKeymap(view.state), event, view, scope);
 | |
| }
 | |
| let storedPrefix = null;
 | |
| const PrefixTimeout = 4000;
 | |
| function buildKeymap(bindings, platform = currentPlatform) {
 | |
|     let bound = Object.create(null);
 | |
|     let isPrefix = Object.create(null);
 | |
|     let checkPrefix = (name, is) => {
 | |
|         let current = isPrefix[name];
 | |
|         if (current == null)
 | |
|             isPrefix[name] = is;
 | |
|         else if (current != is)
 | |
|             throw new Error("Key binding " + name + " is used both as a regular binding and as a multi-stroke prefix");
 | |
|     };
 | |
|     let add = (scope, key, command, preventDefault, stopPropagation) => {
 | |
|         var _a, _b;
 | |
|         let scopeObj = bound[scope] || (bound[scope] = Object.create(null));
 | |
|         let parts = key.split(/ (?!$)/).map(k => normalizeKeyName(k, platform));
 | |
|         for (let i = 1; i < parts.length; i++) {
 | |
|             let prefix = parts.slice(0, i).join(" ");
 | |
|             checkPrefix(prefix, true);
 | |
|             if (!scopeObj[prefix])
 | |
|                 scopeObj[prefix] = {
 | |
|                     preventDefault: true,
 | |
|                     stopPropagation: false,
 | |
|                     run: [(view) => {
 | |
|                             let ourObj = storedPrefix = { view, prefix, scope };
 | |
|                             setTimeout(() => { if (storedPrefix == ourObj)
 | |
|                                 storedPrefix = null; }, PrefixTimeout);
 | |
|                             return true;
 | |
|                         }]
 | |
|                 };
 | |
|         }
 | |
|         let full = parts.join(" ");
 | |
|         checkPrefix(full, false);
 | |
|         let binding = scopeObj[full] || (scopeObj[full] = {
 | |
|             preventDefault: false,
 | |
|             stopPropagation: false,
 | |
|             run: ((_b = (_a = scopeObj._any) === null || _a === void 0 ? void 0 : _a.run) === null || _b === void 0 ? void 0 : _b.slice()) || []
 | |
|         });
 | |
|         if (command)
 | |
|             binding.run.push(command);
 | |
|         if (preventDefault)
 | |
|             binding.preventDefault = true;
 | |
|         if (stopPropagation)
 | |
|             binding.stopPropagation = true;
 | |
|     };
 | |
|     for (let b of bindings) {
 | |
|         let scopes = b.scope ? b.scope.split(" ") : ["editor"];
 | |
|         if (b.any)
 | |
|             for (let scope of scopes) {
 | |
|                 let scopeObj = bound[scope] || (bound[scope] = Object.create(null));
 | |
|                 if (!scopeObj._any)
 | |
|                     scopeObj._any = { preventDefault: false, stopPropagation: false, run: [] };
 | |
|                 let { any } = b;
 | |
|                 for (let key in scopeObj)
 | |
|                     scopeObj[key].run.push(view => any(view, currentKeyEvent));
 | |
|             }
 | |
|         let name = b[platform] || b.key;
 | |
|         if (!name)
 | |
|             continue;
 | |
|         for (let scope of scopes) {
 | |
|             add(scope, name, b.run, b.preventDefault, b.stopPropagation);
 | |
|             if (b.shift)
 | |
|                 add(scope, "Shift-" + name, b.shift, b.preventDefault, b.stopPropagation);
 | |
|         }
 | |
|     }
 | |
|     return bound;
 | |
| }
 | |
| let currentKeyEvent = null;
 | |
| function runHandlers(map, event, view, scope) {
 | |
|     currentKeyEvent = event;
 | |
|     let name = keyName(event);
 | |
|     let charCode = codePointAt(name, 0), isChar = codePointSize(charCode) == name.length && name != " ";
 | |
|     let prefix = "", handled = false, prevented = false, stopPropagation = false;
 | |
|     if (storedPrefix && storedPrefix.view == view && storedPrefix.scope == scope) {
 | |
|         prefix = storedPrefix.prefix + " ";
 | |
|         if (modifierCodes.indexOf(event.keyCode) < 0) {
 | |
|             prevented = true;
 | |
|             storedPrefix = null;
 | |
|         }
 | |
|     }
 | |
|     let ran = new Set;
 | |
|     let runFor = (binding) => {
 | |
|         if (binding) {
 | |
|             for (let cmd of binding.run)
 | |
|                 if (!ran.has(cmd)) {
 | |
|                     ran.add(cmd);
 | |
|                     if (cmd(view)) {
 | |
|                         if (binding.stopPropagation)
 | |
|                             stopPropagation = true;
 | |
|                         return true;
 | |
|                     }
 | |
|                 }
 | |
|             if (binding.preventDefault) {
 | |
|                 if (binding.stopPropagation)
 | |
|                     stopPropagation = true;
 | |
|                 prevented = true;
 | |
|             }
 | |
|         }
 | |
|         return false;
 | |
|     };
 | |
|     let scopeObj = map[scope], baseName, shiftName;
 | |
|     if (scopeObj) {
 | |
|         if (runFor(scopeObj[prefix + modifiers(name, event, !isChar)])) {
 | |
|             handled = true;
 | |
|         }
 | |
|         else if (isChar && (event.altKey || event.metaKey || event.ctrlKey) &&
 | |
|             // Ctrl-Alt may be used for AltGr on Windows
 | |
|             !(browser.windows && event.ctrlKey && event.altKey) &&
 | |
|             (baseName = base[event.keyCode]) && baseName != name) {
 | |
|             if (runFor(scopeObj[prefix + modifiers(baseName, event, true)])) {
 | |
|                 handled = true;
 | |
|             }
 | |
|             else if (event.shiftKey && (shiftName = shift[event.keyCode]) != name && shiftName != baseName &&
 | |
|                 runFor(scopeObj[prefix + modifiers(shiftName, event, false)])) {
 | |
|                 handled = true;
 | |
|             }
 | |
|         }
 | |
|         else if (isChar && event.shiftKey &&
 | |
|             runFor(scopeObj[prefix + modifiers(name, event, true)])) {
 | |
|             handled = true;
 | |
|         }
 | |
|         if (!handled && runFor(scopeObj._any))
 | |
|             handled = true;
 | |
|     }
 | |
|     if (prevented)
 | |
|         handled = true;
 | |
|     if (handled && stopPropagation)
 | |
|         event.stopPropagation();
 | |
|     currentKeyEvent = null;
 | |
|     return handled;
 | |
| }
 | |
| 
 | |
| /**
 | |
| Implementation of [`LayerMarker`](https://codemirror.net/6/docs/ref/#view.LayerMarker) that creates
 | |
| a rectangle at a given set of coordinates.
 | |
| */
 | |
| class RectangleMarker {
 | |
|     /**
 | |
|     Create a marker with the given class and dimensions. If `width`
 | |
|     is null, the DOM element will get no width style.
 | |
|     */
 | |
|     constructor(className, 
 | |
|     /**
 | |
|     The left position of the marker (in pixels, document-relative).
 | |
|     */
 | |
|     left, 
 | |
|     /**
 | |
|     The top position of the marker.
 | |
|     */
 | |
|     top, 
 | |
|     /**
 | |
|     The width of the marker, or null if it shouldn't get a width assigned.
 | |
|     */
 | |
|     width, 
 | |
|     /**
 | |
|     The height of the marker.
 | |
|     */
 | |
|     height) {
 | |
|         this.className = className;
 | |
|         this.left = left;
 | |
|         this.top = top;
 | |
|         this.width = width;
 | |
|         this.height = height;
 | |
|     }
 | |
|     draw() {
 | |
|         let elt = document.createElement("div");
 | |
|         elt.className = this.className;
 | |
|         this.adjust(elt);
 | |
|         return elt;
 | |
|     }
 | |
|     update(elt, prev) {
 | |
|         if (prev.className != this.className)
 | |
|             return false;
 | |
|         this.adjust(elt);
 | |
|         return true;
 | |
|     }
 | |
|     adjust(elt) {
 | |
|         elt.style.left = this.left + "px";
 | |
|         elt.style.top = this.top + "px";
 | |
|         if (this.width != null)
 | |
|             elt.style.width = this.width + "px";
 | |
|         elt.style.height = this.height + "px";
 | |
|     }
 | |
|     eq(p) {
 | |
|         return this.left == p.left && this.top == p.top && this.width == p.width && this.height == p.height &&
 | |
|             this.className == p.className;
 | |
|     }
 | |
|     /**
 | |
|     Create a set of rectangles for the given selection range,
 | |
|     assigning them theclass`className`. Will create a single
 | |
|     rectangle for empty ranges, and a set of selection-style
 | |
|     rectangles covering the range's content (in a bidi-aware
 | |
|     way) for non-empty ones.
 | |
|     */
 | |
|     static forRange(view, className, range) {
 | |
|         if (range.empty) {
 | |
|             let pos = view.coordsAtPos(range.head, range.assoc || 1);
 | |
|             if (!pos)
 | |
|                 return [];
 | |
|             let base = getBase(view);
 | |
|             return [new RectangleMarker(className, pos.left - base.left, pos.top - base.top, null, pos.bottom - pos.top)];
 | |
|         }
 | |
|         else {
 | |
|             return rectanglesForRange(view, className, range);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function getBase(view) {
 | |
|     let rect = view.scrollDOM.getBoundingClientRect();
 | |
|     let left = view.textDirection == Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth * view.scaleX;
 | |
|     return { left: left - view.scrollDOM.scrollLeft * view.scaleX, top: rect.top - view.scrollDOM.scrollTop * view.scaleY };
 | |
| }
 | |
| function wrappedLine(view, pos, side, inside) {
 | |
|     let coords = view.coordsAtPos(pos, side * 2);
 | |
|     if (!coords)
 | |
|         return inside;
 | |
|     let editorRect = view.dom.getBoundingClientRect();
 | |
|     let y = (coords.top + coords.bottom) / 2;
 | |
|     let left = view.posAtCoords({ x: editorRect.left + 1, y });
 | |
|     let right = view.posAtCoords({ x: editorRect.right - 1, y });
 | |
|     if (left == null || right == null)
 | |
|         return inside;
 | |
|     return { from: Math.max(inside.from, Math.min(left, right)), to: Math.min(inside.to, Math.max(left, right)) };
 | |
| }
 | |
| function rectanglesForRange(view, className, range) {
 | |
|     if (range.to <= view.viewport.from || range.from >= view.viewport.to)
 | |
|         return [];
 | |
|     let from = Math.max(range.from, view.viewport.from), to = Math.min(range.to, view.viewport.to);
 | |
|     let ltr = view.textDirection == Direction.LTR;
 | |
|     let content = view.contentDOM, contentRect = content.getBoundingClientRect(), base = getBase(view);
 | |
|     let lineElt = content.querySelector(".cm-line"), lineStyle = lineElt && window.getComputedStyle(lineElt);
 | |
|     let leftSide = contentRect.left +
 | |
|         (lineStyle ? parseInt(lineStyle.paddingLeft) + Math.min(0, parseInt(lineStyle.textIndent)) : 0);
 | |
|     let rightSide = contentRect.right - (lineStyle ? parseInt(lineStyle.paddingRight) : 0);
 | |
|     let startBlock = blockAt(view, from), endBlock = blockAt(view, to);
 | |
|     let visualStart = startBlock.type == BlockType.Text ? startBlock : null;
 | |
|     let visualEnd = endBlock.type == BlockType.Text ? endBlock : null;
 | |
|     if (visualStart && (view.lineWrapping || startBlock.widgetLineBreaks))
 | |
|         visualStart = wrappedLine(view, from, 1, visualStart);
 | |
|     if (visualEnd && (view.lineWrapping || endBlock.widgetLineBreaks))
 | |
|         visualEnd = wrappedLine(view, to, -1, visualEnd);
 | |
|     if (visualStart && visualEnd && visualStart.from == visualEnd.from && visualStart.to == visualEnd.to) {
 | |
|         return pieces(drawForLine(range.from, range.to, visualStart));
 | |
|     }
 | |
|     else {
 | |
|         let top = visualStart ? drawForLine(range.from, null, visualStart) : drawForWidget(startBlock, false);
 | |
|         let bottom = visualEnd ? drawForLine(null, range.to, visualEnd) : drawForWidget(endBlock, true);
 | |
|         let between = [];
 | |
|         if ((visualStart || startBlock).to < (visualEnd || endBlock).from - (visualStart && visualEnd ? 1 : 0) ||
 | |
|             startBlock.widgetLineBreaks > 1 && top.bottom + view.defaultLineHeight / 2 < bottom.top)
 | |
|             between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
 | |
|         else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == BlockType.Text)
 | |
|             top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
 | |
|         return pieces(top).concat(between).concat(pieces(bottom));
 | |
|     }
 | |
|     function piece(left, top, right, bottom) {
 | |
|         return new RectangleMarker(className, left - base.left, top - base.top - 0.01 /* C.Epsilon */, right - left, bottom - top + 0.01 /* C.Epsilon */);
 | |
|     }
 | |
|     function pieces({ top, bottom, horizontal }) {
 | |
|         let pieces = [];
 | |
|         for (let i = 0; i < horizontal.length; i += 2)
 | |
|             pieces.push(piece(horizontal[i], top, horizontal[i + 1], bottom));
 | |
|         return pieces;
 | |
|     }
 | |
|     // Gets passed from/to in line-local positions
 | |
|     function drawForLine(from, to, line) {
 | |
|         let top = 1e9, bottom = -1e9, horizontal = [];
 | |
|         function addSpan(from, fromOpen, to, toOpen, dir) {
 | |
|             // Passing 2/-2 is a kludge to force the view to return
 | |
|             // coordinates on the proper side of block widgets, since
 | |
|             // normalizing the side there, though appropriate for most
 | |
|             // coordsAtPos queries, would break selection drawing.
 | |
|             let fromCoords = view.coordsAtPos(from, (from == line.to ? -2 : 2));
 | |
|             let toCoords = view.coordsAtPos(to, (to == line.from ? 2 : -2));
 | |
|             if (!fromCoords || !toCoords)
 | |
|                 return;
 | |
|             top = Math.min(fromCoords.top, toCoords.top, top);
 | |
|             bottom = Math.max(fromCoords.bottom, toCoords.bottom, bottom);
 | |
|             if (dir == Direction.LTR)
 | |
|                 horizontal.push(ltr && fromOpen ? leftSide : fromCoords.left, ltr && toOpen ? rightSide : toCoords.right);
 | |
|             else
 | |
|                 horizontal.push(!ltr && toOpen ? leftSide : toCoords.left, !ltr && fromOpen ? rightSide : fromCoords.right);
 | |
|         }
 | |
|         let start = from !== null && from !== void 0 ? from : line.from, end = to !== null && to !== void 0 ? to : line.to;
 | |
|         // Split the range by visible range and document line
 | |
|         for (let r of view.visibleRanges)
 | |
|             if (r.to > start && r.from < end) {
 | |
|                 for (let pos = Math.max(r.from, start), endPos = Math.min(r.to, end);;) {
 | |
|                     let docLine = view.state.doc.lineAt(pos);
 | |
|                     for (let span of view.bidiSpans(docLine)) {
 | |
|                         let spanFrom = span.from + docLine.from, spanTo = span.to + docLine.from;
 | |
|                         if (spanFrom >= endPos)
 | |
|                             break;
 | |
|                         if (spanTo > pos)
 | |
|                             addSpan(Math.max(spanFrom, pos), from == null && spanFrom <= start, Math.min(spanTo, endPos), to == null && spanTo >= end, span.dir);
 | |
|                     }
 | |
|                     pos = docLine.to + 1;
 | |
|                     if (pos >= endPos)
 | |
|                         break;
 | |
|                 }
 | |
|             }
 | |
|         if (horizontal.length == 0)
 | |
|             addSpan(start, from == null, end, to == null, view.textDirection);
 | |
|         return { top, bottom, horizontal };
 | |
|     }
 | |
|     function drawForWidget(block, top) {
 | |
|         let y = contentRect.top + (top ? block.top : block.bottom);
 | |
|         return { top: y, bottom: y, horizontal: [] };
 | |
|     }
 | |
| }
 | |
| function sameMarker(a, b) {
 | |
|     return a.constructor == b.constructor && a.eq(b);
 | |
| }
 | |
| class LayerView {
 | |
|     constructor(view, layer) {
 | |
|         this.view = view;
 | |
|         this.layer = layer;
 | |
|         this.drawn = [];
 | |
|         this.scaleX = 1;
 | |
|         this.scaleY = 1;
 | |
|         this.measureReq = { read: this.measure.bind(this), write: this.draw.bind(this) };
 | |
|         this.dom = view.scrollDOM.appendChild(document.createElement("div"));
 | |
|         this.dom.classList.add("cm-layer");
 | |
|         if (layer.above)
 | |
|             this.dom.classList.add("cm-layer-above");
 | |
|         if (layer.class)
 | |
|             this.dom.classList.add(layer.class);
 | |
|         this.scale();
 | |
|         this.dom.setAttribute("aria-hidden", "true");
 | |
|         this.setOrder(view.state);
 | |
|         view.requestMeasure(this.measureReq);
 | |
|         if (layer.mount)
 | |
|             layer.mount(this.dom, view);
 | |
|     }
 | |
|     update(update) {
 | |
|         if (update.startState.facet(layerOrder) != update.state.facet(layerOrder))
 | |
|             this.setOrder(update.state);
 | |
|         if (this.layer.update(update, this.dom) || update.geometryChanged) {
 | |
|             this.scale();
 | |
|             update.view.requestMeasure(this.measureReq);
 | |
|         }
 | |
|     }
 | |
|     docViewUpdate(view) {
 | |
|         if (this.layer.updateOnDocViewUpdate !== false)
 | |
|             view.requestMeasure(this.measureReq);
 | |
|     }
 | |
|     setOrder(state) {
 | |
|         let pos = 0, order = state.facet(layerOrder);
 | |
|         while (pos < order.length && order[pos] != this.layer)
 | |
|             pos++;
 | |
|         this.dom.style.zIndex = String((this.layer.above ? 150 : -1) - pos);
 | |
|     }
 | |
|     measure() {
 | |
|         return this.layer.markers(this.view);
 | |
|     }
 | |
|     scale() {
 | |
|         let { scaleX, scaleY } = this.view;
 | |
|         if (scaleX != this.scaleX || scaleY != this.scaleY) {
 | |
|             this.scaleX = scaleX;
 | |
|             this.scaleY = scaleY;
 | |
|             this.dom.style.transform = `scale(${1 / scaleX}, ${1 / scaleY})`;
 | |
|         }
 | |
|     }
 | |
|     draw(markers) {
 | |
|         if (markers.length != this.drawn.length || markers.some((p, i) => !sameMarker(p, this.drawn[i]))) {
 | |
|             let old = this.dom.firstChild, oldI = 0;
 | |
|             for (let marker of markers) {
 | |
|                 if (marker.update && old && marker.constructor && this.drawn[oldI].constructor &&
 | |
|                     marker.update(old, this.drawn[oldI])) {
 | |
|                     old = old.nextSibling;
 | |
|                     oldI++;
 | |
|                 }
 | |
|                 else {
 | |
|                     this.dom.insertBefore(marker.draw(), old);
 | |
|                 }
 | |
|             }
 | |
|             while (old) {
 | |
|                 let next = old.nextSibling;
 | |
|                 old.remove();
 | |
|                 old = next;
 | |
|             }
 | |
|             this.drawn = markers;
 | |
|         }
 | |
|     }
 | |
|     destroy() {
 | |
|         if (this.layer.destroy)
 | |
|             this.layer.destroy(this.dom, this.view);
 | |
|         this.dom.remove();
 | |
|     }
 | |
| }
 | |
| const layerOrder = /*@__PURE__*/Facet.define();
 | |
| /**
 | |
| Define a layer.
 | |
| */
 | |
| function layer(config) {
 | |
|     return [
 | |
|         ViewPlugin.define(v => new LayerView(v, config)),
 | |
|         layerOrder.of(config)
 | |
|     ];
 | |
| }
 | |
| 
 | |
| const CanHidePrimary = !browser.ios; // FIXME test IE
 | |
| const selectionConfig = /*@__PURE__*/Facet.define({
 | |
|     combine(configs) {
 | |
|         return combineConfig(configs, {
 | |
|             cursorBlinkRate: 1200,
 | |
|             drawRangeCursor: true
 | |
|         }, {
 | |
|             cursorBlinkRate: (a, b) => Math.min(a, b),
 | |
|             drawRangeCursor: (a, b) => a || b
 | |
|         });
 | |
|     }
 | |
| });
 | |
| /**
 | |
| Returns an extension that hides the browser's native selection and
 | |
| cursor, replacing the selection with a background behind the text
 | |
| (with the `cm-selectionBackground` class), and the
 | |
| cursors with elements overlaid over the code (using
 | |
| `cm-cursor-primary` and `cm-cursor-secondary`).
 | |
| 
 | |
| This allows the editor to display secondary selection ranges, and
 | |
| tends to produce a type of selection more in line with that users
 | |
| expect in a text editor (the native selection styling will often
 | |
| leave gaps between lines and won't fill the horizontal space after
 | |
| a line when the selection continues past it).
 | |
| 
 | |
| It does have a performance cost, in that it requires an extra DOM
 | |
| layout cycle for many updates (the selection is drawn based on DOM
 | |
| layout information that's only available after laying out the
 | |
| content).
 | |
| */
 | |
| function drawSelection(config = {}) {
 | |
|     return [
 | |
|         selectionConfig.of(config),
 | |
|         cursorLayer,
 | |
|         selectionLayer,
 | |
|         hideNativeSelection,
 | |
|         nativeSelectionHidden.of(true)
 | |
|     ];
 | |
| }
 | |
| /**
 | |
| Retrieve the [`drawSelection`](https://codemirror.net/6/docs/ref/#view.drawSelection) configuration
 | |
| for this state. (Note that this will return a set of defaults even
 | |
| if `drawSelection` isn't enabled.)
 | |
| */
 | |
| function getDrawSelectionConfig(state) {
 | |
|     return state.facet(selectionConfig);
 | |
| }
 | |
| function configChanged(update) {
 | |
|     return update.startState.facet(selectionConfig) != update.state.facet(selectionConfig);
 | |
| }
 | |
| const cursorLayer = /*@__PURE__*/layer({
 | |
|     above: true,
 | |
|     markers(view) {
 | |
|         let { state } = view, conf = state.facet(selectionConfig);
 | |
|         let cursors = [];
 | |
|         for (let r of state.selection.ranges) {
 | |
|             let prim = r == state.selection.main;
 | |
|             if (r.empty ? !prim || CanHidePrimary : conf.drawRangeCursor) {
 | |
|                 let className = prim ? "cm-cursor cm-cursor-primary" : "cm-cursor cm-cursor-secondary";
 | |
|                 let cursor = r.empty ? r : EditorSelection.cursor(r.head, r.head > r.anchor ? -1 : 1);
 | |
|                 for (let piece of RectangleMarker.forRange(view, className, cursor))
 | |
|                     cursors.push(piece);
 | |
|             }
 | |
|         }
 | |
|         return cursors;
 | |
|     },
 | |
|     update(update, dom) {
 | |
|         if (update.transactions.some(tr => tr.selection))
 | |
|             dom.style.animationName = dom.style.animationName == "cm-blink" ? "cm-blink2" : "cm-blink";
 | |
|         let confChange = configChanged(update);
 | |
|         if (confChange)
 | |
|             setBlinkRate(update.state, dom);
 | |
|         return update.docChanged || update.selectionSet || confChange;
 | |
|     },
 | |
|     mount(dom, view) {
 | |
|         setBlinkRate(view.state, dom);
 | |
|     },
 | |
|     class: "cm-cursorLayer"
 | |
| });
 | |
| function setBlinkRate(state, dom) {
 | |
|     dom.style.animationDuration = state.facet(selectionConfig).cursorBlinkRate + "ms";
 | |
| }
 | |
| const selectionLayer = /*@__PURE__*/layer({
 | |
|     above: false,
 | |
|     markers(view) {
 | |
|         return view.state.selection.ranges.map(r => r.empty ? [] : RectangleMarker.forRange(view, "cm-selectionBackground", r))
 | |
|             .reduce((a, b) => a.concat(b));
 | |
|     },
 | |
|     update(update, dom) {
 | |
|         return update.docChanged || update.selectionSet || update.viewportChanged || configChanged(update);
 | |
|     },
 | |
|     class: "cm-selectionLayer"
 | |
| });
 | |
| const themeSpec = {
 | |
|     ".cm-line": {
 | |
|         "& ::selection, &::selection": { backgroundColor: "transparent !important" },
 | |
|     },
 | |
|     ".cm-content": {
 | |
|         "& :focus": {
 | |
|             caretColor: "initial !important",
 | |
|             "&::selection, & ::selection": {
 | |
|                 backgroundColor: "Highlight !important"
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| };
 | |
| if (CanHidePrimary)
 | |
|     themeSpec[".cm-line"].caretColor = themeSpec[".cm-content"].caretColor = "transparent !important";
 | |
| const hideNativeSelection = /*@__PURE__*/Prec.highest(/*@__PURE__*/EditorView.theme(themeSpec));
 | |
| 
 | |
| const setDropCursorPos = /*@__PURE__*/StateEffect.define({
 | |
|     map(pos, mapping) { return pos == null ? null : mapping.mapPos(pos); }
 | |
| });
 | |
| const dropCursorPos = /*@__PURE__*/StateField.define({
 | |
|     create() { return null; },
 | |
|     update(pos, tr) {
 | |
|         if (pos != null)
 | |
|             pos = tr.changes.mapPos(pos);
 | |
|         return tr.effects.reduce((pos, e) => e.is(setDropCursorPos) ? e.value : pos, pos);
 | |
|     }
 | |
| });
 | |
| const drawDropCursor = /*@__PURE__*/ViewPlugin.fromClass(class {
 | |
|     constructor(view) {
 | |
|         this.view = view;
 | |
|         this.cursor = null;
 | |
|         this.measureReq = { read: this.readPos.bind(this), write: this.drawCursor.bind(this) };
 | |
|     }
 | |
|     update(update) {
 | |
|         var _a;
 | |
|         let cursorPos = update.state.field(dropCursorPos);
 | |
|         if (cursorPos == null) {
 | |
|             if (this.cursor != null) {
 | |
|                 (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.remove();
 | |
|                 this.cursor = null;
 | |
|             }
 | |
|         }
 | |
|         else {
 | |
|             if (!this.cursor) {
 | |
|                 this.cursor = this.view.scrollDOM.appendChild(document.createElement("div"));
 | |
|                 this.cursor.className = "cm-dropCursor";
 | |
|             }
 | |
|             if (update.startState.field(dropCursorPos) != cursorPos || update.docChanged || update.geometryChanged)
 | |
|                 this.view.requestMeasure(this.measureReq);
 | |
|         }
 | |
|     }
 | |
|     readPos() {
 | |
|         let { view } = this;
 | |
|         let pos = view.state.field(dropCursorPos);
 | |
|         let rect = pos != null && view.coordsAtPos(pos);
 | |
|         if (!rect)
 | |
|             return null;
 | |
|         let outer = view.scrollDOM.getBoundingClientRect();
 | |
|         return {
 | |
|             left: rect.left - outer.left + view.scrollDOM.scrollLeft * view.scaleX,
 | |
|             top: rect.top - outer.top + view.scrollDOM.scrollTop * view.scaleY,
 | |
|             height: rect.bottom - rect.top
 | |
|         };
 | |
|     }
 | |
|     drawCursor(pos) {
 | |
|         if (this.cursor) {
 | |
|             let { scaleX, scaleY } = this.view;
 | |
|             if (pos) {
 | |
|                 this.cursor.style.left = pos.left / scaleX + "px";
 | |
|                 this.cursor.style.top = pos.top / scaleY + "px";
 | |
|                 this.cursor.style.height = pos.height / scaleY + "px";
 | |
|             }
 | |
|             else {
 | |
|                 this.cursor.style.left = "-100000px";
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     destroy() {
 | |
|         if (this.cursor)
 | |
|             this.cursor.remove();
 | |
|     }
 | |
|     setDropPos(pos) {
 | |
|         if (this.view.state.field(dropCursorPos) != pos)
 | |
|             this.view.dispatch({ effects: setDropCursorPos.of(pos) });
 | |
|     }
 | |
| }, {
 | |
|     eventObservers: {
 | |
|         dragover(event) {
 | |
|             this.setDropPos(this.view.posAtCoords({ x: event.clientX, y: event.clientY }));
 | |
|         },
 | |
|         dragleave(event) {
 | |
|             if (event.target == this.view.contentDOM || !this.view.contentDOM.contains(event.relatedTarget))
 | |
|                 this.setDropPos(null);
 | |
|         },
 | |
|         dragend() {
 | |
|             this.setDropPos(null);
 | |
|         },
 | |
|         drop() {
 | |
|             this.setDropPos(null);
 | |
|         }
 | |
|     }
 | |
| });
 | |
| /**
 | |
| Draws a cursor at the current drop position when something is
 | |
| dragged over the editor.
 | |
| */
 | |
| function dropCursor() {
 | |
|     return [dropCursorPos, drawDropCursor];
 | |
| }
 | |
| 
 | |
| function iterMatches(doc, re, from, to, f) {
 | |
|     re.lastIndex = 0;
 | |
|     for (let cursor = doc.iterRange(from, to), pos = from, m; !cursor.next().done; pos += cursor.value.length) {
 | |
|         if (!cursor.lineBreak)
 | |
|             while (m = re.exec(cursor.value))
 | |
|                 f(pos + m.index, m);
 | |
|     }
 | |
| }
 | |
| function matchRanges(view, maxLength) {
 | |
|     let visible = view.visibleRanges;
 | |
|     if (visible.length == 1 && visible[0].from == view.viewport.from &&
 | |
|         visible[0].to == view.viewport.to)
 | |
|         return visible;
 | |
|     let result = [];
 | |
|     for (let { from, to } of visible) {
 | |
|         from = Math.max(view.state.doc.lineAt(from).from, from - maxLength);
 | |
|         to = Math.min(view.state.doc.lineAt(to).to, to + maxLength);
 | |
|         if (result.length && result[result.length - 1].to >= from)
 | |
|             result[result.length - 1].to = to;
 | |
|         else
 | |
|             result.push({ from, to });
 | |
|     }
 | |
|     return result;
 | |
| }
 | |
| /**
 | |
| Helper class used to make it easier to maintain decorations on
 | |
| visible code that matches a given regular expression. To be used
 | |
| in a [view plugin](https://codemirror.net/6/docs/ref/#view.ViewPlugin). Instances of this object
 | |
| represent a matching configuration.
 | |
| */
 | |
| class MatchDecorator {
 | |
|     /**
 | |
|     Create a decorator.
 | |
|     */
 | |
|     constructor(config) {
 | |
|         const { regexp, decoration, decorate, boundary, maxLength = 1000 } = config;
 | |
|         if (!regexp.global)
 | |
|             throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
 | |
|         this.regexp = regexp;
 | |
|         if (decorate) {
 | |
|             this.addMatch = (match, view, from, add) => decorate(add, from, from + match[0].length, match, view);
 | |
|         }
 | |
|         else if (typeof decoration == "function") {
 | |
|             this.addMatch = (match, view, from, add) => {
 | |
|                 let deco = decoration(match, view, from);
 | |
|                 if (deco)
 | |
|                     add(from, from + match[0].length, deco);
 | |
|             };
 | |
|         }
 | |
|         else if (decoration) {
 | |
|             this.addMatch = (match, _view, from, add) => add(from, from + match[0].length, decoration);
 | |
|         }
 | |
|         else {
 | |
|             throw new RangeError("Either 'decorate' or 'decoration' should be provided to MatchDecorator");
 | |
|         }
 | |
|         this.boundary = boundary;
 | |
|         this.maxLength = maxLength;
 | |
|     }
 | |
|     /**
 | |
|     Compute the full set of decorations for matches in the given
 | |
|     view's viewport. You'll want to call this when initializing your
 | |
|     plugin.
 | |
|     */
 | |
|     createDeco(view) {
 | |
|         let build = new RangeSetBuilder(), add = build.add.bind(build);
 | |
|         for (let { from, to } of matchRanges(view, this.maxLength))
 | |
|             iterMatches(view.state.doc, this.regexp, from, to, (from, m) => this.addMatch(m, view, from, add));
 | |
|         return build.finish();
 | |
|     }
 | |
|     /**
 | |
|     Update a set of decorations for a view update. `deco` _must_ be
 | |
|     the set of decorations produced by _this_ `MatchDecorator` for
 | |
|     the view state before the update.
 | |
|     */
 | |
|     updateDeco(update, deco) {
 | |
|         let changeFrom = 1e9, changeTo = -1;
 | |
|         if (update.docChanged)
 | |
|             update.changes.iterChanges((_f, _t, from, to) => {
 | |
|                 if (to > update.view.viewport.from && from < update.view.viewport.to) {
 | |
|                     changeFrom = Math.min(from, changeFrom);
 | |
|                     changeTo = Math.max(to, changeTo);
 | |
|                 }
 | |
|             });
 | |
|         if (update.viewportChanged || changeTo - changeFrom > 1000)
 | |
|             return this.createDeco(update.view);
 | |
|         if (changeTo > -1)
 | |
|             return this.updateRange(update.view, deco.map(update.changes), changeFrom, changeTo);
 | |
|         return deco;
 | |
|     }
 | |
|     updateRange(view, deco, updateFrom, updateTo) {
 | |
|         for (let r of view.visibleRanges) {
 | |
|             let from = Math.max(r.from, updateFrom), to = Math.min(r.to, updateTo);
 | |
|             if (to > from) {
 | |
|                 let fromLine = view.state.doc.lineAt(from), toLine = fromLine.to < to ? view.state.doc.lineAt(to) : fromLine;
 | |
|                 let start = Math.max(r.from, fromLine.from), end = Math.min(r.to, toLine.to);
 | |
|                 if (this.boundary) {
 | |
|                     for (; from > fromLine.from; from--)
 | |
|                         if (this.boundary.test(fromLine.text[from - 1 - fromLine.from])) {
 | |
|                             start = from;
 | |
|                             break;
 | |
|                         }
 | |
|                     for (; to < toLine.to; to++)
 | |
|                         if (this.boundary.test(toLine.text[to - toLine.from])) {
 | |
|                             end = to;
 | |
|                             break;
 | |
|                         }
 | |
|                 }
 | |
|                 let ranges = [], m;
 | |
|                 let add = (from, to, deco) => ranges.push(deco.range(from, to));
 | |
|                 if (fromLine == toLine) {
 | |
|                     this.regexp.lastIndex = start - fromLine.from;
 | |
|                     while ((m = this.regexp.exec(fromLine.text)) && m.index < end - fromLine.from)
 | |
|                         this.addMatch(m, view, m.index + fromLine.from, add);
 | |
|                 }
 | |
|                 else {
 | |
|                     iterMatches(view.state.doc, this.regexp, start, end, (from, m) => this.addMatch(m, view, from, add));
 | |
|                 }
 | |
|                 deco = deco.update({ filterFrom: start, filterTo: end, filter: (from, to) => from < start || to > end, add: ranges });
 | |
|             }
 | |
|         }
 | |
|         return deco;
 | |
|     }
 | |
| }
 | |
| 
 | |
| const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
 | |
| const Specials = /*@__PURE__*/new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
 | |
| const Names = {
 | |
|     0: "null",
 | |
|     7: "bell",
 | |
|     8: "backspace",
 | |
|     10: "newline",
 | |
|     11: "vertical tab",
 | |
|     13: "carriage return",
 | |
|     27: "escape",
 | |
|     8203: "zero width space",
 | |
|     8204: "zero width non-joiner",
 | |
|     8205: "zero width joiner",
 | |
|     8206: "left-to-right mark",
 | |
|     8207: "right-to-left mark",
 | |
|     8232: "line separator",
 | |
|     8237: "left-to-right override",
 | |
|     8238: "right-to-left override",
 | |
|     8294: "left-to-right isolate",
 | |
|     8295: "right-to-left isolate",
 | |
|     8297: "pop directional isolate",
 | |
|     8233: "paragraph separator",
 | |
|     65279: "zero width no-break space",
 | |
|     65532: "object replacement"
 | |
| };
 | |
| let _supportsTabSize = null;
 | |
| function supportsTabSize() {
 | |
|     var _a;
 | |
|     if (_supportsTabSize == null && typeof document != "undefined" && document.body) {
 | |
|         let styles = document.body.style;
 | |
|         _supportsTabSize = ((_a = styles.tabSize) !== null && _a !== void 0 ? _a : styles.MozTabSize) != null;
 | |
|     }
 | |
|     return _supportsTabSize || false;
 | |
| }
 | |
| const specialCharConfig = /*@__PURE__*/Facet.define({
 | |
|     combine(configs) {
 | |
|         let config = combineConfig(configs, {
 | |
|             render: null,
 | |
|             specialChars: Specials,
 | |
|             addSpecialChars: null
 | |
|         });
 | |
|         if (config.replaceTabs = !supportsTabSize())
 | |
|             config.specialChars = new RegExp("\t|" + config.specialChars.source, UnicodeRegexpSupport);
 | |
|         if (config.addSpecialChars)
 | |
|             config.specialChars = new RegExp(config.specialChars.source + "|" + config.addSpecialChars.source, UnicodeRegexpSupport);
 | |
|         return config;
 | |
|     }
 | |
| });
 | |
| /**
 | |
| Returns an extension that installs highlighting of special
 | |
| characters.
 | |
| */
 | |
| function highlightSpecialChars(
 | |
| /**
 | |
| Configuration options.
 | |
| */
 | |
| config = {}) {
 | |
|     return [specialCharConfig.of(config), specialCharPlugin()];
 | |
| }
 | |
| let _plugin = null;
 | |
| function specialCharPlugin() {
 | |
|     return _plugin || (_plugin = ViewPlugin.fromClass(class {
 | |
|         constructor(view) {
 | |
|             this.view = view;
 | |
|             this.decorations = Decoration.none;
 | |
|             this.decorationCache = Object.create(null);
 | |
|             this.decorator = this.makeDecorator(view.state.facet(specialCharConfig));
 | |
|             this.decorations = this.decorator.createDeco(view);
 | |
|         }
 | |
|         makeDecorator(conf) {
 | |
|             return new MatchDecorator({
 | |
|                 regexp: conf.specialChars,
 | |
|                 decoration: (m, view, pos) => {
 | |
|                     let { doc } = view.state;
 | |
|                     let code = codePointAt(m[0], 0);
 | |
|                     if (code == 9) {
 | |
|                         let line = doc.lineAt(pos);
 | |
|                         let size = view.state.tabSize, col = countColumn(line.text, size, pos - line.from);
 | |
|                         return Decoration.replace({
 | |
|                             widget: new TabWidget((size - (col % size)) * this.view.defaultCharacterWidth / this.view.scaleX)
 | |
|                         });
 | |
|                     }
 | |
|                     return this.decorationCache[code] ||
 | |
|                         (this.decorationCache[code] = Decoration.replace({ widget: new SpecialCharWidget(conf, code) }));
 | |
|                 },
 | |
|                 boundary: conf.replaceTabs ? undefined : /[^]/
 | |
|             });
 | |
|         }
 | |
|         update(update) {
 | |
|             let conf = update.state.facet(specialCharConfig);
 | |
|             if (update.startState.facet(specialCharConfig) != conf) {
 | |
|                 this.decorator = this.makeDecorator(conf);
 | |
|                 this.decorations = this.decorator.createDeco(update.view);
 | |
|             }
 | |
|             else {
 | |
|                 this.decorations = this.decorator.updateDeco(update, this.decorations);
 | |
|             }
 | |
|         }
 | |
|     }, {
 | |
|         decorations: v => v.decorations
 | |
|     }));
 | |
| }
 | |
| const DefaultPlaceholder = "\u2022";
 | |
| // Assigns placeholder characters from the Control Pictures block to
 | |
| // ASCII control characters
 | |
| function placeholder$1(code) {
 | |
|     if (code >= 32)
 | |
|         return DefaultPlaceholder;
 | |
|     if (code == 10)
 | |
|         return "\u2424";
 | |
|     return String.fromCharCode(9216 + code);
 | |
| }
 | |
| class SpecialCharWidget extends WidgetType {
 | |
|     constructor(options, code) {
 | |
|         super();
 | |
|         this.options = options;
 | |
|         this.code = code;
 | |
|     }
 | |
|     eq(other) { return other.code == this.code; }
 | |
|     toDOM(view) {
 | |
|         let ph = placeholder$1(this.code);
 | |
|         let desc = view.state.phrase("Control character") + " " + (Names[this.code] || "0x" + this.code.toString(16));
 | |
|         let custom = this.options.render && this.options.render(this.code, desc, ph);
 | |
|         if (custom)
 | |
|             return custom;
 | |
|         let span = document.createElement("span");
 | |
|         span.textContent = ph;
 | |
|         span.title = desc;
 | |
|         span.setAttribute("aria-label", desc);
 | |
|         span.className = "cm-specialChar";
 | |
|         return span;
 | |
|     }
 | |
|     ignoreEvent() { return false; }
 | |
| }
 | |
| class TabWidget extends WidgetType {
 | |
|     constructor(width) {
 | |
|         super();
 | |
|         this.width = width;
 | |
|     }
 | |
|     eq(other) { return other.width == this.width; }
 | |
|     toDOM() {
 | |
|         let span = document.createElement("span");
 | |
|         span.textContent = "\t";
 | |
|         span.className = "cm-tab";
 | |
|         span.style.width = this.width + "px";
 | |
|         return span;
 | |
|     }
 | |
|     ignoreEvent() { return false; }
 | |
| }
 | |
| 
 | |
| const plugin = /*@__PURE__*/ViewPlugin.fromClass(class {
 | |
|     constructor() {
 | |
|         this.height = 1000;
 | |
|         this.attrs = { style: "padding-bottom: 1000px" };
 | |
|     }
 | |
|     update(update) {
 | |
|         let { view } = update;
 | |
|         let height = view.viewState.editorHeight -
 | |
|             view.defaultLineHeight - view.documentPadding.top - 0.5;
 | |
|         if (height >= 0 && height != this.height) {
 | |
|             this.height = height;
 | |
|             this.attrs = { style: `padding-bottom: ${height}px` };
 | |
|         }
 | |
|     }
 | |
| });
 | |
| /**
 | |
| Returns an extension that makes sure the content has a bottom
 | |
| margin equivalent to the height of the editor, minus one line
 | |
| height, so that every line in the document can be scrolled to the
 | |
| top of the editor.
 | |
| 
 | |
| This is only meaningful when the editor is scrollable, and should
 | |
| not be enabled in editors that take the size of their content.
 | |
| */
 | |
| function scrollPastEnd() {
 | |
|     return [plugin, contentAttributes.of(view => { var _a; return ((_a = view.plugin(plugin)) === null || _a === void 0 ? void 0 : _a.attrs) || null; })];
 | |
| }
 | |
| 
 | |
| /**
 | |
| Mark lines that have a cursor on them with the `"cm-activeLine"`
 | |
| DOM class.
 | |
| */
 | |
| function highlightActiveLine() {
 | |
|     return activeLineHighlighter;
 | |
| }
 | |
| const lineDeco = /*@__PURE__*/Decoration.line({ class: "cm-activeLine" });
 | |
| const activeLineHighlighter = /*@__PURE__*/ViewPlugin.fromClass(class {
 | |
|     constructor(view) {
 | |
|         this.decorations = this.getDeco(view);
 | |
|     }
 | |
|     update(update) {
 | |
|         if (update.docChanged || update.selectionSet)
 | |
|             this.decorations = this.getDeco(update.view);
 | |
|     }
 | |
|     getDeco(view) {
 | |
|         let lastLineStart = -1, deco = [];
 | |
|         for (let r of view.state.selection.ranges) {
 | |
|             let line = view.lineBlockAt(r.head);
 | |
|             if (line.from > lastLineStart) {
 | |
|                 deco.push(lineDeco.range(line.from));
 | |
|                 lastLineStart = line.from;
 | |
|             }
 | |
|         }
 | |
|         return Decoration.set(deco);
 | |
|     }
 | |
| }, {
 | |
|     decorations: v => v.decorations
 | |
| });
 | |
| 
 | |
| class Placeholder extends WidgetType {
 | |
|     constructor(content) {
 | |
|         super();
 | |
|         this.content = content;
 | |
|     }
 | |
|     toDOM() {
 | |
|         let wrap = document.createElement("span");
 | |
|         wrap.className = "cm-placeholder";
 | |
|         wrap.style.pointerEvents = "none";
 | |
|         wrap.appendChild(typeof this.content == "string" ? document.createTextNode(this.content) : this.content);
 | |
|         if (typeof this.content == "string")
 | |
|             wrap.setAttribute("aria-label", "placeholder " + this.content);
 | |
|         else
 | |
|             wrap.setAttribute("aria-hidden", "true");
 | |
|         return wrap;
 | |
|     }
 | |
|     coordsAt(dom) {
 | |
|         let rects = dom.firstChild ? clientRectsFor(dom.firstChild) : [];
 | |
|         if (!rects.length)
 | |
|             return null;
 | |
|         let style = window.getComputedStyle(dom.parentNode);
 | |
|         let rect = flattenRect(rects[0], style.direction != "rtl");
 | |
|         let lineHeight = parseInt(style.lineHeight);
 | |
|         if (rect.bottom - rect.top > lineHeight * 1.5)
 | |
|             return { left: rect.left, right: rect.right, top: rect.top, bottom: rect.top + lineHeight };
 | |
|         return rect;
 | |
|     }
 | |
|     ignoreEvent() { return false; }
 | |
| }
 | |
| /**
 | |
| Extension that enables a placeholder—a piece of example content
 | |
| to show when the editor is empty.
 | |
| */
 | |
| function placeholder(content) {
 | |
|     return ViewPlugin.fromClass(class {
 | |
|         constructor(view) {
 | |
|             this.view = view;
 | |
|             this.placeholder = content
 | |
|                 ? Decoration.set([Decoration.widget({ widget: new Placeholder(content), side: 1 }).range(0)])
 | |
|                 : Decoration.none;
 | |
|         }
 | |
|         get decorations() { return this.view.state.doc.length ? Decoration.none : this.placeholder; }
 | |
|     }, { decorations: v => v.decorations });
 | |
| }
 | |
| 
 | |
| // Don't compute precise column positions for line offsets above this
 | |
| // (since it could get expensive). Assume offset==column for them.
 | |
| const MaxOff = 2000;
 | |
| function rectangleFor(state, a, b) {
 | |
|     let startLine = Math.min(a.line, b.line), endLine = Math.max(a.line, b.line);
 | |
|     let ranges = [];
 | |
|     if (a.off > MaxOff || b.off > MaxOff || a.col < 0 || b.col < 0) {
 | |
|         let startOff = Math.min(a.off, b.off), endOff = Math.max(a.off, b.off);
 | |
|         for (let i = startLine; i <= endLine; i++) {
 | |
|             let line = state.doc.line(i);
 | |
|             if (line.length <= endOff)
 | |
|                 ranges.push(EditorSelection.range(line.from + startOff, line.to + endOff));
 | |
|         }
 | |
|     }
 | |
|     else {
 | |
|         let startCol = Math.min(a.col, b.col), endCol = Math.max(a.col, b.col);
 | |
|         for (let i = startLine; i <= endLine; i++) {
 | |
|             let line = state.doc.line(i);
 | |
|             let start = findColumn(line.text, startCol, state.tabSize, true);
 | |
|             if (start < 0) {
 | |
|                 ranges.push(EditorSelection.cursor(line.to));
 | |
|             }
 | |
|             else {
 | |
|                 let end = findColumn(line.text, endCol, state.tabSize);
 | |
|                 ranges.push(EditorSelection.range(line.from + start, line.from + end));
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     return ranges;
 | |
| }
 | |
| function absoluteColumn(view, x) {
 | |
|     let ref = view.coordsAtPos(view.viewport.from);
 | |
|     return ref ? Math.round(Math.abs((ref.left - x) / view.defaultCharacterWidth)) : -1;
 | |
| }
 | |
| function getPos(view, event) {
 | |
|     let offset = view.posAtCoords({ x: event.clientX, y: event.clientY }, false);
 | |
|     let line = view.state.doc.lineAt(offset), off = offset - line.from;
 | |
|     let col = off > MaxOff ? -1
 | |
|         : off == line.length ? absoluteColumn(view, event.clientX)
 | |
|             : countColumn(line.text, view.state.tabSize, offset - line.from);
 | |
|     return { line: line.number, col, off };
 | |
| }
 | |
| function rectangleSelectionStyle(view, event) {
 | |
|     let start = getPos(view, event), startSel = view.state.selection;
 | |
|     if (!start)
 | |
|         return null;
 | |
|     return {
 | |
|         update(update) {
 | |
|             if (update.docChanged) {
 | |
|                 let newStart = update.changes.mapPos(update.startState.doc.line(start.line).from);
 | |
|                 let newLine = update.state.doc.lineAt(newStart);
 | |
|                 start = { line: newLine.number, col: start.col, off: Math.min(start.off, newLine.length) };
 | |
|                 startSel = startSel.map(update.changes);
 | |
|             }
 | |
|         },
 | |
|         get(event, _extend, multiple) {
 | |
|             let cur = getPos(view, event);
 | |
|             if (!cur)
 | |
|                 return startSel;
 | |
|             let ranges = rectangleFor(view.state, start, cur);
 | |
|             if (!ranges.length)
 | |
|                 return startSel;
 | |
|             if (multiple)
 | |
|                 return EditorSelection.create(ranges.concat(startSel.ranges));
 | |
|             else
 | |
|                 return EditorSelection.create(ranges);
 | |
|         }
 | |
|     };
 | |
| }
 | |
| /**
 | |
| Create an extension that enables rectangular selections. By
 | |
| default, it will react to left mouse drag with the Alt key held
 | |
| down. When such a selection occurs, the text within the rectangle
 | |
| that was dragged over will be selected, as one selection
 | |
| [range](https://codemirror.net/6/docs/ref/#state.SelectionRange) per line.
 | |
| */
 | |
| function rectangularSelection(options) {
 | |
|     let filter = (options === null || options === void 0 ? void 0 : options.eventFilter) || (e => e.altKey && e.button == 0);
 | |
|     return EditorView.mouseSelectionStyle.of((view, event) => filter(event) ? rectangleSelectionStyle(view, event) : null);
 | |
| }
 | |
| const keys = {
 | |
|     Alt: [18, e => !!e.altKey],
 | |
|     Control: [17, e => !!e.ctrlKey],
 | |
|     Shift: [16, e => !!e.shiftKey],
 | |
|     Meta: [91, e => !!e.metaKey]
 | |
| };
 | |
| const showCrosshair = { style: "cursor: crosshair" };
 | |
| /**
 | |
| Returns an extension that turns the pointer cursor into a
 | |
| crosshair when a given modifier key, defaulting to Alt, is held
 | |
| down. Can serve as a visual hint that rectangular selection is
 | |
| going to happen when paired with
 | |
| [`rectangularSelection`](https://codemirror.net/6/docs/ref/#view.rectangularSelection).
 | |
| */
 | |
| function crosshairCursor(options = {}) {
 | |
|     let [code, getter] = keys[options.key || "Alt"];
 | |
|     let plugin = ViewPlugin.fromClass(class {
 | |
|         constructor(view) {
 | |
|             this.view = view;
 | |
|             this.isDown = false;
 | |
|         }
 | |
|         set(isDown) {
 | |
|             if (this.isDown != isDown) {
 | |
|                 this.isDown = isDown;
 | |
|                 this.view.update([]);
 | |
|             }
 | |
|         }
 | |
|     }, {
 | |
|         eventObservers: {
 | |
|             keydown(e) {
 | |
|                 this.set(e.keyCode == code || getter(e));
 | |
|             },
 | |
|             keyup(e) {
 | |
|                 if (e.keyCode == code || !getter(e))
 | |
|                     this.set(false);
 | |
|             },
 | |
|             mousemove(e) {
 | |
|                 this.set(getter(e));
 | |
|             }
 | |
|         }
 | |
|     });
 | |
|     return [
 | |
|         plugin,
 | |
|         EditorView.contentAttributes.of(view => { var _a; return ((_a = view.plugin(plugin)) === null || _a === void 0 ? void 0 : _a.isDown) ? showCrosshair : null; })
 | |
|     ];
 | |
| }
 | |
| 
 | |
| const Outside = "-10000px";
 | |
| class TooltipViewManager {
 | |
|     constructor(view, facet, createTooltipView, removeTooltipView) {
 | |
|         this.facet = facet;
 | |
|         this.createTooltipView = createTooltipView;
 | |
|         this.removeTooltipView = removeTooltipView;
 | |
|         this.input = view.state.facet(facet);
 | |
|         this.tooltips = this.input.filter(t => t);
 | |
|         let prev = null;
 | |
|         this.tooltipViews = this.tooltips.map(t => prev = createTooltipView(t, prev));
 | |
|     }
 | |
|     update(update, above) {
 | |
|         var _a;
 | |
|         let input = update.state.facet(this.facet);
 | |
|         let tooltips = input.filter(x => x);
 | |
|         if (input === this.input) {
 | |
|             for (let t of this.tooltipViews)
 | |
|                 if (t.update)
 | |
|                     t.update(update);
 | |
|             return false;
 | |
|         }
 | |
|         let tooltipViews = [], newAbove = above ? [] : null;
 | |
|         for (let i = 0; i < tooltips.length; i++) {
 | |
|             let tip = tooltips[i], known = -1;
 | |
|             if (!tip)
 | |
|                 continue;
 | |
|             for (let i = 0; i < this.tooltips.length; i++) {
 | |
|                 let other = this.tooltips[i];
 | |
|                 if (other && other.create == tip.create)
 | |
|                     known = i;
 | |
|             }
 | |
|             if (known < 0) {
 | |
|                 tooltipViews[i] = this.createTooltipView(tip, i ? tooltipViews[i - 1] : null);
 | |
|                 if (newAbove)
 | |
|                     newAbove[i] = !!tip.above;
 | |
|             }
 | |
|             else {
 | |
|                 let tooltipView = tooltipViews[i] = this.tooltipViews[known];
 | |
|                 if (newAbove)
 | |
|                     newAbove[i] = above[known];
 | |
|                 if (tooltipView.update)
 | |
|                     tooltipView.update(update);
 | |
|             }
 | |
|         }
 | |
|         for (let t of this.tooltipViews)
 | |
|             if (tooltipViews.indexOf(t) < 0) {
 | |
|                 this.removeTooltipView(t);
 | |
|                 (_a = t.destroy) === null || _a === void 0 ? void 0 : _a.call(t);
 | |
|             }
 | |
|         if (above) {
 | |
|             newAbove.forEach((val, i) => above[i] = val);
 | |
|             above.length = newAbove.length;
 | |
|         }
 | |
|         this.input = input;
 | |
|         this.tooltips = tooltips;
 | |
|         this.tooltipViews = tooltipViews;
 | |
|         return true;
 | |
|     }
 | |
| }
 | |
| /**
 | |
| Creates an extension that configures tooltip behavior.
 | |
| */
 | |
| function tooltips(config = {}) {
 | |
|     return tooltipConfig.of(config);
 | |
| }
 | |
| function windowSpace(view) {
 | |
|     let { win } = view;
 | |
|     return { top: 0, left: 0, bottom: win.innerHeight, right: win.innerWidth };
 | |
| }
 | |
| const tooltipConfig = /*@__PURE__*/Facet.define({
 | |
|     combine: values => {
 | |
|         var _a, _b, _c;
 | |
|         return ({
 | |
|             position: browser.ios ? "absolute" : ((_a = values.find(conf => conf.position)) === null || _a === void 0 ? void 0 : _a.position) || "fixed",
 | |
|             parent: ((_b = values.find(conf => conf.parent)) === null || _b === void 0 ? void 0 : _b.parent) || null,
 | |
|             tooltipSpace: ((_c = values.find(conf => conf.tooltipSpace)) === null || _c === void 0 ? void 0 : _c.tooltipSpace) || windowSpace,
 | |
|         });
 | |
|     }
 | |
| });
 | |
| const knownHeight = /*@__PURE__*/new WeakMap();
 | |
| const tooltipPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
 | |
|     constructor(view) {
 | |
|         this.view = view;
 | |
|         this.above = [];
 | |
|         this.inView = true;
 | |
|         this.madeAbsolute = false;
 | |
|         this.lastTransaction = 0;
 | |
|         this.measureTimeout = -1;
 | |
|         let config = view.state.facet(tooltipConfig);
 | |
|         this.position = config.position;
 | |
|         this.parent = config.parent;
 | |
|         this.classes = view.themeClasses;
 | |
|         this.createContainer();
 | |
|         this.measureReq = { read: this.readMeasure.bind(this), write: this.writeMeasure.bind(this), key: this };
 | |
|         this.resizeObserver = typeof ResizeObserver == "function" ? new ResizeObserver(() => this.measureSoon()) : null;
 | |
|         this.manager = new TooltipViewManager(view, showTooltip, (t, p) => this.createTooltip(t, p), t => {
 | |
|             if (this.resizeObserver)
 | |
|                 this.resizeObserver.unobserve(t.dom);
 | |
|             t.dom.remove();
 | |
|         });
 | |
|         this.above = this.manager.tooltips.map(t => !!t.above);
 | |
|         this.intersectionObserver = typeof IntersectionObserver == "function" ? new IntersectionObserver(entries => {
 | |
|             if (Date.now() > this.lastTransaction - 50 &&
 | |
|                 entries.length > 0 && entries[entries.length - 1].intersectionRatio < 1)
 | |
|                 this.measureSoon();
 | |
|         }, { threshold: [1] }) : null;
 | |
|         this.observeIntersection();
 | |
|         view.win.addEventListener("resize", this.measureSoon = this.measureSoon.bind(this));
 | |
|         this.maybeMeasure();
 | |
|     }
 | |
|     createContainer() {
 | |
|         if (this.parent) {
 | |
|             this.container = document.createElement("div");
 | |
|             this.container.style.position = "relative";
 | |
|             this.container.className = this.view.themeClasses;
 | |
|             this.parent.appendChild(this.container);
 | |
|         }
 | |
|         else {
 | |
|             this.container = this.view.dom;
 | |
|         }
 | |
|     }
 | |
|     observeIntersection() {
 | |
|         if (this.intersectionObserver) {
 | |
|             this.intersectionObserver.disconnect();
 | |
|             for (let tooltip of this.manager.tooltipViews)
 | |
|                 this.intersectionObserver.observe(tooltip.dom);
 | |
|         }
 | |
|     }
 | |
|     measureSoon() {
 | |
|         if (this.measureTimeout < 0)
 | |
|             this.measureTimeout = setTimeout(() => {
 | |
|                 this.measureTimeout = -1;
 | |
|                 this.maybeMeasure();
 | |
|             }, 50);
 | |
|     }
 | |
|     update(update) {
 | |
|         if (update.transactions.length)
 | |
|             this.lastTransaction = Date.now();
 | |
|         let updated = this.manager.update(update, this.above);
 | |
|         if (updated)
 | |
|             this.observeIntersection();
 | |
|         let shouldMeasure = updated || update.geometryChanged;
 | |
|         let newConfig = update.state.facet(tooltipConfig);
 | |
|         if (newConfig.position != this.position && !this.madeAbsolute) {
 | |
|             this.position = newConfig.position;
 | |
|             for (let t of this.manager.tooltipViews)
 | |
|                 t.dom.style.position = this.position;
 | |
|             shouldMeasure = true;
 | |
|         }
 | |
|         if (newConfig.parent != this.parent) {
 | |
|             if (this.parent)
 | |
|                 this.container.remove();
 | |
|             this.parent = newConfig.parent;
 | |
|             this.createContainer();
 | |
|             for (let t of this.manager.tooltipViews)
 | |
|                 this.container.appendChild(t.dom);
 | |
|             shouldMeasure = true;
 | |
|         }
 | |
|         else if (this.parent && this.view.themeClasses != this.classes) {
 | |
|             this.classes = this.container.className = this.view.themeClasses;
 | |
|         }
 | |
|         if (shouldMeasure)
 | |
|             this.maybeMeasure();
 | |
|     }
 | |
|     createTooltip(tooltip, prev) {
 | |
|         let tooltipView = tooltip.create(this.view);
 | |
|         let before = prev ? prev.dom : null;
 | |
|         tooltipView.dom.classList.add("cm-tooltip");
 | |
|         if (tooltip.arrow && !tooltipView.dom.querySelector(".cm-tooltip > .cm-tooltip-arrow")) {
 | |
|             let arrow = document.createElement("div");
 | |
|             arrow.className = "cm-tooltip-arrow";
 | |
|             tooltipView.dom.appendChild(arrow);
 | |
|         }
 | |
|         tooltipView.dom.style.position = this.position;
 | |
|         tooltipView.dom.style.top = Outside;
 | |
|         tooltipView.dom.style.left = "0px";
 | |
|         this.container.insertBefore(tooltipView.dom, before);
 | |
|         if (tooltipView.mount)
 | |
|             tooltipView.mount(this.view);
 | |
|         if (this.resizeObserver)
 | |
|             this.resizeObserver.observe(tooltipView.dom);
 | |
|         return tooltipView;
 | |
|     }
 | |
|     destroy() {
 | |
|         var _a, _b, _c;
 | |
|         this.view.win.removeEventListener("resize", this.measureSoon);
 | |
|         for (let tooltipView of this.manager.tooltipViews) {
 | |
|             tooltipView.dom.remove();
 | |
|             (_a = tooltipView.destroy) === null || _a === void 0 ? void 0 : _a.call(tooltipView);
 | |
|         }
 | |
|         if (this.parent)
 | |
|             this.container.remove();
 | |
|         (_b = this.resizeObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
 | |
|         (_c = this.intersectionObserver) === null || _c === void 0 ? void 0 : _c.disconnect();
 | |
|         clearTimeout(this.measureTimeout);
 | |
|     }
 | |
|     readMeasure() {
 | |
|         let editor = this.view.dom.getBoundingClientRect();
 | |
|         let scaleX = 1, scaleY = 1, makeAbsolute = false;
 | |
|         if (this.position == "fixed" && this.manager.tooltipViews.length) {
 | |
|             let { dom } = this.manager.tooltipViews[0];
 | |
|             if (browser.gecko) {
 | |
|                 // Firefox sets the element's `offsetParent` to the
 | |
|                 // transformed element when a transform interferes with fixed
 | |
|                 // positioning.
 | |
|                 makeAbsolute = dom.offsetParent != this.container.ownerDocument.body;
 | |
|             }
 | |
|             else if (dom.style.top == Outside && dom.style.left == "0px") {
 | |
|                 // On other browsers, we have to awkwardly try and use other
 | |
|                 // information to detect a transform.
 | |
|                 let rect = dom.getBoundingClientRect();
 | |
|                 makeAbsolute = Math.abs(rect.top + 10000) > 1 || Math.abs(rect.left) > 1;
 | |
|             }
 | |
|         }
 | |
|         if (makeAbsolute || this.position == "absolute") {
 | |
|             if (this.parent) {
 | |
|                 let rect = this.parent.getBoundingClientRect();
 | |
|                 if (rect.width && rect.height) {
 | |
|                     scaleX = rect.width / this.parent.offsetWidth;
 | |
|                     scaleY = rect.height / this.parent.offsetHeight;
 | |
|                 }
 | |
|             }
 | |
|             else {
 | |
|                 ({ scaleX, scaleY } = this.view.viewState);
 | |
|             }
 | |
|         }
 | |
|         return {
 | |
|             editor,
 | |
|             parent: this.parent ? this.container.getBoundingClientRect() : editor,
 | |
|             pos: this.manager.tooltips.map((t, i) => {
 | |
|                 let tv = this.manager.tooltipViews[i];
 | |
|                 return tv.getCoords ? tv.getCoords(t.pos) : this.view.coordsAtPos(t.pos);
 | |
|             }),
 | |
|             size: this.manager.tooltipViews.map(({ dom }) => dom.getBoundingClientRect()),
 | |
|             space: this.view.state.facet(tooltipConfig).tooltipSpace(this.view),
 | |
|             scaleX, scaleY, makeAbsolute
 | |
|         };
 | |
|     }
 | |
|     writeMeasure(measured) {
 | |
|         var _a;
 | |
|         if (measured.makeAbsolute) {
 | |
|             this.madeAbsolute = true;
 | |
|             this.position = "absolute";
 | |
|             for (let t of this.manager.tooltipViews)
 | |
|                 t.dom.style.position = "absolute";
 | |
|         }
 | |
|         let { editor, space, scaleX, scaleY } = measured;
 | |
|         let others = [];
 | |
|         for (let i = 0; i < this.manager.tooltips.length; i++) {
 | |
|             let tooltip = this.manager.tooltips[i], tView = this.manager.tooltipViews[i], { dom } = tView;
 | |
|             let pos = measured.pos[i], size = measured.size[i];
 | |
|             // Hide tooltips that are outside of the editor.
 | |
|             if (!pos || pos.bottom <= Math.max(editor.top, space.top) ||
 | |
|                 pos.top >= Math.min(editor.bottom, space.bottom) ||
 | |
|                 pos.right < Math.max(editor.left, space.left) - .1 ||
 | |
|                 pos.left > Math.min(editor.right, space.right) + .1) {
 | |
|                 dom.style.top = Outside;
 | |
|                 continue;
 | |
|             }
 | |
|             let arrow = tooltip.arrow ? tView.dom.querySelector(".cm-tooltip-arrow") : null;
 | |
|             let arrowHeight = arrow ? 7 /* Arrow.Size */ : 0;
 | |
|             let width = size.right - size.left, height = (_a = knownHeight.get(tView)) !== null && _a !== void 0 ? _a : size.bottom - size.top;
 | |
|             let offset = tView.offset || noOffset, ltr = this.view.textDirection == Direction.LTR;
 | |
|             let left = size.width > space.right - space.left
 | |
|                 ? (ltr ? space.left : space.right - size.width)
 | |
|                 : ltr ? Math.max(space.left, Math.min(pos.left - (arrow ? 14 /* Arrow.Offset */ : 0) + offset.x, space.right - width))
 | |
|                     : Math.min(Math.max(space.left, pos.left - width + (arrow ? 14 /* Arrow.Offset */ : 0) - offset.x), space.right - width);
 | |
|             let above = this.above[i];
 | |
|             if (!tooltip.strictSide && (above
 | |
|                 ? pos.top - (size.bottom - size.top) - offset.y < space.top
 | |
|                 : pos.bottom + (size.bottom - size.top) + offset.y > space.bottom) &&
 | |
|                 above == (space.bottom - pos.bottom > pos.top - space.top))
 | |
|                 above = this.above[i] = !above;
 | |
|             let spaceVert = (above ? pos.top - space.top : space.bottom - pos.bottom) - arrowHeight;
 | |
|             if (spaceVert < height && tView.resize !== false) {
 | |
|                 if (spaceVert < this.view.defaultLineHeight) {
 | |
|                     dom.style.top = Outside;
 | |
|                     continue;
 | |
|                 }
 | |
|                 knownHeight.set(tView, height);
 | |
|                 dom.style.height = (height = spaceVert) / scaleY + "px";
 | |
|             }
 | |
|             else if (dom.style.height) {
 | |
|                 dom.style.height = "";
 | |
|             }
 | |
|             let top = above ? pos.top - height - arrowHeight - offset.y : pos.bottom + arrowHeight + offset.y;
 | |
|             let right = left + width;
 | |
|             if (tView.overlap !== true)
 | |
|                 for (let r of others)
 | |
|                     if (r.left < right && r.right > left && r.top < top + height && r.bottom > top)
 | |
|                         top = above ? r.top - height - 2 - arrowHeight : r.bottom + arrowHeight + 2;
 | |
|             if (this.position == "absolute") {
 | |
|                 dom.style.top = (top - measured.parent.top) / scaleY + "px";
 | |
|                 dom.style.left = (left - measured.parent.left) / scaleX + "px";
 | |
|             }
 | |
|             else {
 | |
|                 dom.style.top = top / scaleY + "px";
 | |
|                 dom.style.left = left / scaleX + "px";
 | |
|             }
 | |
|             if (arrow) {
 | |
|                 let arrowLeft = pos.left + (ltr ? offset.x : -offset.x) - (left + 14 /* Arrow.Offset */ - 7 /* Arrow.Size */);
 | |
|                 arrow.style.left = arrowLeft / scaleX + "px";
 | |
|             }
 | |
|             if (tView.overlap !== true)
 | |
|                 others.push({ left, top, right, bottom: top + height });
 | |
|             dom.classList.toggle("cm-tooltip-above", above);
 | |
|             dom.classList.toggle("cm-tooltip-below", !above);
 | |
|             if (tView.positioned)
 | |
|                 tView.positioned(measured.space);
 | |
|         }
 | |
|     }
 | |
|     maybeMeasure() {
 | |
|         if (this.manager.tooltips.length) {
 | |
|             if (this.view.inView)
 | |
|                 this.view.requestMeasure(this.measureReq);
 | |
|             if (this.inView != this.view.inView) {
 | |
|                 this.inView = this.view.inView;
 | |
|                 if (!this.inView)
 | |
|                     for (let tv of this.manager.tooltipViews)
 | |
|                         tv.dom.style.top = Outside;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }, {
 | |
|     eventObservers: {
 | |
|         scroll() { this.maybeMeasure(); }
 | |
|     }
 | |
| });
 | |
| const baseTheme = /*@__PURE__*/EditorView.baseTheme({
 | |
|     ".cm-tooltip": {
 | |
|         zIndex: 100,
 | |
|         boxSizing: "border-box"
 | |
|     },
 | |
|     "&light .cm-tooltip": {
 | |
|         border: "1px solid #bbb",
 | |
|         backgroundColor: "#f5f5f5"
 | |
|     },
 | |
|     "&light .cm-tooltip-section:not(:first-child)": {
 | |
|         borderTop: "1px solid #bbb",
 | |
|     },
 | |
|     "&dark .cm-tooltip": {
 | |
|         backgroundColor: "#333338",
 | |
|         color: "white"
 | |
|     },
 | |
|     ".cm-tooltip-arrow": {
 | |
|         height: `${7 /* Arrow.Size */}px`,
 | |
|         width: `${7 /* Arrow.Size */ * 2}px`,
 | |
|         position: "absolute",
 | |
|         zIndex: -1,
 | |
|         overflow: "hidden",
 | |
|         "&:before, &:after": {
 | |
|             content: "''",
 | |
|             position: "absolute",
 | |
|             width: 0,
 | |
|             height: 0,
 | |
|             borderLeft: `${7 /* Arrow.Size */}px solid transparent`,
 | |
|             borderRight: `${7 /* Arrow.Size */}px solid transparent`,
 | |
|         },
 | |
|         ".cm-tooltip-above &": {
 | |
|             bottom: `-${7 /* Arrow.Size */}px`,
 | |
|             "&:before": {
 | |
|                 borderTop: `${7 /* Arrow.Size */}px solid #bbb`,
 | |
|             },
 | |
|             "&:after": {
 | |
|                 borderTop: `${7 /* Arrow.Size */}px solid #f5f5f5`,
 | |
|                 bottom: "1px"
 | |
|             }
 | |
|         },
 | |
|         ".cm-tooltip-below &": {
 | |
|             top: `-${7 /* Arrow.Size */}px`,
 | |
|             "&:before": {
 | |
|                 borderBottom: `${7 /* Arrow.Size */}px solid #bbb`,
 | |
|             },
 | |
|             "&:after": {
 | |
|                 borderBottom: `${7 /* Arrow.Size */}px solid #f5f5f5`,
 | |
|                 top: "1px"
 | |
|             }
 | |
|         },
 | |
|     },
 | |
|     "&dark .cm-tooltip .cm-tooltip-arrow": {
 | |
|         "&:before": {
 | |
|             borderTopColor: "#333338",
 | |
|             borderBottomColor: "#333338"
 | |
|         },
 | |
|         "&:after": {
 | |
|             borderTopColor: "transparent",
 | |
|             borderBottomColor: "transparent"
 | |
|         }
 | |
|     }
 | |
| });
 | |
| const noOffset = { x: 0, y: 0 };
 | |
| /**
 | |
| Facet to which an extension can add a value to show a tooltip.
 | |
| */
 | |
| const showTooltip = /*@__PURE__*/Facet.define({
 | |
|     enables: [tooltipPlugin, baseTheme]
 | |
| });
 | |
| const showHoverTooltip = /*@__PURE__*/Facet.define({
 | |
|     combine: inputs => inputs.reduce((a, i) => a.concat(i), [])
 | |
| });
 | |
| class HoverTooltipHost {
 | |
|     // Needs to be static so that host tooltip instances always match
 | |
|     static create(view) {
 | |
|         return new HoverTooltipHost(view);
 | |
|     }
 | |
|     constructor(view) {
 | |
|         this.view = view;
 | |
|         this.mounted = false;
 | |
|         this.dom = document.createElement("div");
 | |
|         this.dom.classList.add("cm-tooltip-hover");
 | |
|         this.manager = new TooltipViewManager(view, showHoverTooltip, (t, p) => this.createHostedView(t, p), t => t.dom.remove());
 | |
|     }
 | |
|     createHostedView(tooltip, prev) {
 | |
|         let hostedView = tooltip.create(this.view);
 | |
|         hostedView.dom.classList.add("cm-tooltip-section");
 | |
|         this.dom.insertBefore(hostedView.dom, prev ? prev.dom.nextSibling : this.dom.firstChild);
 | |
|         if (this.mounted && hostedView.mount)
 | |
|             hostedView.mount(this.view);
 | |
|         return hostedView;
 | |
|     }
 | |
|     mount(view) {
 | |
|         for (let hostedView of this.manager.tooltipViews) {
 | |
|             if (hostedView.mount)
 | |
|                 hostedView.mount(view);
 | |
|         }
 | |
|         this.mounted = true;
 | |
|     }
 | |
|     positioned(space) {
 | |
|         for (let hostedView of this.manager.tooltipViews) {
 | |
|             if (hostedView.positioned)
 | |
|                 hostedView.positioned(space);
 | |
|         }
 | |
|     }
 | |
|     update(update) {
 | |
|         this.manager.update(update);
 | |
|     }
 | |
|     destroy() {
 | |
|         var _a;
 | |
|         for (let t of this.manager.tooltipViews)
 | |
|             (_a = t.destroy) === null || _a === void 0 ? void 0 : _a.call(t);
 | |
|     }
 | |
|     passProp(name) {
 | |
|         let value = undefined;
 | |
|         for (let view of this.manager.tooltipViews) {
 | |
|             let given = view[name];
 | |
|             if (given !== undefined) {
 | |
|                 if (value === undefined)
 | |
|                     value = given;
 | |
|                 else if (value !== given)
 | |
|                     return undefined;
 | |
|             }
 | |
|         }
 | |
|         return value;
 | |
|     }
 | |
|     get offset() { return this.passProp("offset"); }
 | |
|     get getCoords() { return this.passProp("getCoords"); }
 | |
|     get overlap() { return this.passProp("overlap"); }
 | |
|     get resize() { return this.passProp("resize"); }
 | |
| }
 | |
| const showHoverTooltipHost = /*@__PURE__*/showTooltip.compute([showHoverTooltip], state => {
 | |
|     let tooltips = state.facet(showHoverTooltip);
 | |
|     if (tooltips.length === 0)
 | |
|         return null;
 | |
|     return {
 | |
|         pos: Math.min(...tooltips.map(t => t.pos)),
 | |
|         end: Math.max(...tooltips.map(t => { var _a; return (_a = t.end) !== null && _a !== void 0 ? _a : t.pos; })),
 | |
|         create: HoverTooltipHost.create,
 | |
|         above: tooltips[0].above,
 | |
|         arrow: tooltips.some(t => t.arrow),
 | |
|     };
 | |
| });
 | |
| class HoverPlugin {
 | |
|     constructor(view, source, field, setHover, hoverTime) {
 | |
|         this.view = view;
 | |
|         this.source = source;
 | |
|         this.field = field;
 | |
|         this.setHover = setHover;
 | |
|         this.hoverTime = hoverTime;
 | |
|         this.hoverTimeout = -1;
 | |
|         this.restartTimeout = -1;
 | |
|         this.pending = null;
 | |
|         this.lastMove = { x: 0, y: 0, target: view.dom, time: 0 };
 | |
|         this.checkHover = this.checkHover.bind(this);
 | |
|         view.dom.addEventListener("mouseleave", this.mouseleave = this.mouseleave.bind(this));
 | |
|         view.dom.addEventListener("mousemove", this.mousemove = this.mousemove.bind(this));
 | |
|     }
 | |
|     update() {
 | |
|         if (this.pending) {
 | |
|             this.pending = null;
 | |
|             clearTimeout(this.restartTimeout);
 | |
|             this.restartTimeout = setTimeout(() => this.startHover(), 20);
 | |
|         }
 | |
|     }
 | |
|     get active() {
 | |
|         return this.view.state.field(this.field);
 | |
|     }
 | |
|     checkHover() {
 | |
|         this.hoverTimeout = -1;
 | |
|         if (this.active.length)
 | |
|             return;
 | |
|         let hovered = Date.now() - this.lastMove.time;
 | |
|         if (hovered < this.hoverTime)
 | |
|             this.hoverTimeout = setTimeout(this.checkHover, this.hoverTime - hovered);
 | |
|         else
 | |
|             this.startHover();
 | |
|     }
 | |
|     startHover() {
 | |
|         clearTimeout(this.restartTimeout);
 | |
|         let { view, lastMove } = this;
 | |
|         let desc = view.docView.nearest(lastMove.target);
 | |
|         if (!desc)
 | |
|             return;
 | |
|         let pos, side = 1;
 | |
|         if (desc instanceof WidgetView) {
 | |
|             pos = desc.posAtStart;
 | |
|         }
 | |
|         else {
 | |
|             pos = view.posAtCoords(lastMove);
 | |
|             if (pos == null)
 | |
|                 return;
 | |
|             let posCoords = view.coordsAtPos(pos);
 | |
|             if (!posCoords ||
 | |
|                 lastMove.y < posCoords.top || lastMove.y > posCoords.bottom ||
 | |
|                 lastMove.x < posCoords.left - view.defaultCharacterWidth ||
 | |
|                 lastMove.x > posCoords.right + view.defaultCharacterWidth)
 | |
|                 return;
 | |
|             let bidi = view.bidiSpans(view.state.doc.lineAt(pos)).find(s => s.from <= pos && s.to >= pos);
 | |
|             let rtl = bidi && bidi.dir == Direction.RTL ? -1 : 1;
 | |
|             side = (lastMove.x < posCoords.left ? -rtl : rtl);
 | |
|         }
 | |
|         let open = this.source(view, pos, side);
 | |
|         if (open === null || open === void 0 ? void 0 : open.then) {
 | |
|             let pending = this.pending = { pos };
 | |
|             open.then(result => {
 | |
|                 if (this.pending == pending) {
 | |
|                     this.pending = null;
 | |
|                     if (result && !(Array.isArray(result) && !result.length))
 | |
|                         view.dispatch({ effects: this.setHover.of(Array.isArray(result) ? result : [result]) });
 | |
|                 }
 | |
|             }, e => logException(view.state, e, "hover tooltip"));
 | |
|         }
 | |
|         else if (open && !(Array.isArray(open) && !open.length)) {
 | |
|             view.dispatch({ effects: this.setHover.of(Array.isArray(open) ? open : [open]) });
 | |
|         }
 | |
|     }
 | |
|     get tooltip() {
 | |
|         let plugin = this.view.plugin(tooltipPlugin);
 | |
|         let index = plugin ? plugin.manager.tooltips.findIndex(t => t.create == HoverTooltipHost.create) : -1;
 | |
|         return index > -1 ? plugin.manager.tooltipViews[index] : null;
 | |
|     }
 | |
|     mousemove(event) {
 | |
|         var _a, _b;
 | |
|         this.lastMove = { x: event.clientX, y: event.clientY, target: event.target, time: Date.now() };
 | |
|         if (this.hoverTimeout < 0)
 | |
|             this.hoverTimeout = setTimeout(this.checkHover, this.hoverTime);
 | |
|         let { active, tooltip } = this;
 | |
|         if (active.length && tooltip && !isInTooltip(tooltip.dom, event) || this.pending) {
 | |
|             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;
 | |
|             if ((pos == end ? this.view.posAtCoords(this.lastMove) != pos
 | |
|                 : !isOverRange(this.view, pos, end, event.clientX, event.clientY))) {
 | |
|                 this.view.dispatch({ effects: this.setHover.of([]) });
 | |
|                 this.pending = null;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     mouseleave(event) {
 | |
|         clearTimeout(this.hoverTimeout);
 | |
|         this.hoverTimeout = -1;
 | |
|         let { active } = this;
 | |
|         if (active.length) {
 | |
|             let { tooltip } = this;
 | |
|             let inTooltip = tooltip && tooltip.dom.contains(event.relatedTarget);
 | |
|             if (!inTooltip)
 | |
|                 this.view.dispatch({ effects: this.setHover.of([]) });
 | |
|             else
 | |
|                 this.watchTooltipLeave(tooltip.dom);
 | |
|         }
 | |
|     }
 | |
|     watchTooltipLeave(tooltip) {
 | |
|         let watch = (event) => {
 | |
|             tooltip.removeEventListener("mouseleave", watch);
 | |
|             if (this.active.length && !this.view.dom.contains(event.relatedTarget))
 | |
|                 this.view.dispatch({ effects: this.setHover.of([]) });
 | |
|         };
 | |
|         tooltip.addEventListener("mouseleave", watch);
 | |
|     }
 | |
|     destroy() {
 | |
|         clearTimeout(this.hoverTimeout);
 | |
|         this.view.dom.removeEventListener("mouseleave", this.mouseleave);
 | |
|         this.view.dom.removeEventListener("mousemove", this.mousemove);
 | |
|     }
 | |
| }
 | |
| const tooltipMargin = 4;
 | |
| function isInTooltip(tooltip, event) {
 | |
|     let rect = tooltip.getBoundingClientRect();
 | |
|     return event.clientX >= rect.left - tooltipMargin && event.clientX <= rect.right + tooltipMargin &&
 | |
|         event.clientY >= rect.top - tooltipMargin && event.clientY <= rect.bottom + tooltipMargin;
 | |
| }
 | |
| function isOverRange(view, from, to, x, y, margin) {
 | |
|     let rect = view.scrollDOM.getBoundingClientRect();
 | |
|     let docBottom = view.documentTop + view.documentPadding.top + view.contentHeight;
 | |
|     if (rect.left > x || rect.right < x || rect.top > y || Math.min(rect.bottom, docBottom) < y)
 | |
|         return false;
 | |
|     let pos = view.posAtCoords({ x, y }, false);
 | |
|     return pos >= from && pos <= to;
 | |
| }
 | |
| /**
 | |
| Set up a hover tooltip, which shows up when the pointer hovers
 | |
| over ranges of text. The callback is called when the mouse hovers
 | |
| over the document text. It should, if there is a tooltip
 | |
| associated with position `pos`, return the tooltip description
 | |
| (either directly or in a promise). The `side` argument indicates
 | |
| on which side of the position the pointer is—it will be -1 if the
 | |
| pointer is before the position, 1 if after the position.
 | |
| 
 | |
| Note that all hover tooltips are hosted within a single tooltip
 | |
| container element. This allows multiple tooltips over the same
 | |
| range to be "merged" together without overlapping.
 | |
| 
 | |
| The return value is a valid [editor extension](https://codemirror.net/6/docs/ref/#state.Extension)
 | |
| but also provides an `active` property holding a state field that
 | |
| can be used to read the currently active tooltips produced by this
 | |
| extension.
 | |
| */
 | |
| function hoverTooltip(source, options = {}) {
 | |
|     let setHover = StateEffect.define();
 | |
|     let hoverState = StateField.define({
 | |
|         create() { return []; },
 | |
|         update(value, tr) {
 | |
|             if (value.length) {
 | |
|                 if (options.hideOnChange && (tr.docChanged || tr.selection))
 | |
|                     value = [];
 | |
|                 else if (options.hideOn)
 | |
|                     value = value.filter(v => !options.hideOn(tr, v));
 | |
|                 if (tr.docChanged) {
 | |
|                     let mapped = [];
 | |
|                     for (let tooltip of value) {
 | |
|                         let newPos = tr.changes.mapPos(tooltip.pos, -1, MapMode.TrackDel);
 | |
|                         if (newPos != null) {
 | |
|                             let copy = Object.assign(Object.create(null), tooltip);
 | |
|                             copy.pos = newPos;
 | |
|                             if (copy.end != null)
 | |
|                                 copy.end = tr.changes.mapPos(copy.end);
 | |
|                             mapped.push(copy);
 | |
|                         }
 | |
|                     }
 | |
|                     value = mapped;
 | |
|                 }
 | |
|             }
 | |
|             for (let effect of tr.effects) {
 | |
|                 if (effect.is(setHover))
 | |
|                     value = effect.value;
 | |
|                 if (effect.is(closeHoverTooltipEffect))
 | |
|                     value = [];
 | |
|             }
 | |
|             return value;
 | |
|         },
 | |
|         provide: f => showHoverTooltip.from(f)
 | |
|     });
 | |
|     return {
 | |
|         active: hoverState,
 | |
|         extension: [
 | |
|             hoverState,
 | |
|             ViewPlugin.define(view => new HoverPlugin(view, source, hoverState, setHover, options.hoverTime || 300 /* Hover.Time */)),
 | |
|             showHoverTooltipHost
 | |
|         ]
 | |
|     };
 | |
| }
 | |
| /**
 | |
| Get the active tooltip view for a given tooltip, if available.
 | |
| */
 | |
| function getTooltip(view, tooltip) {
 | |
|     let plugin = view.plugin(tooltipPlugin);
 | |
|     if (!plugin)
 | |
|         return null;
 | |
|     let found = plugin.manager.tooltips.indexOf(tooltip);
 | |
|     return found < 0 ? null : plugin.manager.tooltipViews[found];
 | |
| }
 | |
| /**
 | |
| Returns true if any hover tooltips are currently active.
 | |
| */
 | |
| function hasHoverTooltips(state) {
 | |
|     return state.facet(showHoverTooltip).some(x => x);
 | |
| }
 | |
| const closeHoverTooltipEffect = /*@__PURE__*/StateEffect.define();
 | |
| /**
 | |
| Transaction effect that closes all hover tooltips.
 | |
| */
 | |
| const closeHoverTooltips = /*@__PURE__*/closeHoverTooltipEffect.of(null);
 | |
| /**
 | |
| Tell the tooltip extension to recompute the position of the active
 | |
| tooltips. This can be useful when something happens (such as a
 | |
| re-positioning or CSS change affecting the editor) that could
 | |
| invalidate the existing tooltip positions.
 | |
| */
 | |
| function repositionTooltips(view) {
 | |
|     let plugin = view.plugin(tooltipPlugin);
 | |
|     if (plugin)
 | |
|         plugin.maybeMeasure();
 | |
| }
 | |
| 
 | |
| const panelConfig = /*@__PURE__*/Facet.define({
 | |
|     combine(configs) {
 | |
|         let topContainer, bottomContainer;
 | |
|         for (let c of configs) {
 | |
|             topContainer = topContainer || c.topContainer;
 | |
|             bottomContainer = bottomContainer || c.bottomContainer;
 | |
|         }
 | |
|         return { topContainer, bottomContainer };
 | |
|     }
 | |
| });
 | |
| /**
 | |
| Configures the panel-managing extension.
 | |
| */
 | |
| function panels(config) {
 | |
|     return config ? [panelConfig.of(config)] : [];
 | |
| }
 | |
| /**
 | |
| Get the active panel created by the given constructor, if any.
 | |
| This can be useful when you need access to your panels' DOM
 | |
| structure.
 | |
| */
 | |
| function getPanel(view, panel) {
 | |
|     let plugin = view.plugin(panelPlugin);
 | |
|     let index = plugin ? plugin.specs.indexOf(panel) : -1;
 | |
|     return index > -1 ? plugin.panels[index] : null;
 | |
| }
 | |
| const panelPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
 | |
|     constructor(view) {
 | |
|         this.input = view.state.facet(showPanel);
 | |
|         this.specs = this.input.filter(s => s);
 | |
|         this.panels = this.specs.map(spec => spec(view));
 | |
|         let conf = view.state.facet(panelConfig);
 | |
|         this.top = new PanelGroup(view, true, conf.topContainer);
 | |
|         this.bottom = new PanelGroup(view, false, conf.bottomContainer);
 | |
|         this.top.sync(this.panels.filter(p => p.top));
 | |
|         this.bottom.sync(this.panels.filter(p => !p.top));
 | |
|         for (let p of this.panels) {
 | |
|             p.dom.classList.add("cm-panel");
 | |
|             if (p.mount)
 | |
|                 p.mount();
 | |
|         }
 | |
|     }
 | |
|     update(update) {
 | |
|         let conf = update.state.facet(panelConfig);
 | |
|         if (this.top.container != conf.topContainer) {
 | |
|             this.top.sync([]);
 | |
|             this.top = new PanelGroup(update.view, true, conf.topContainer);
 | |
|         }
 | |
|         if (this.bottom.container != conf.bottomContainer) {
 | |
|             this.bottom.sync([]);
 | |
|             this.bottom = new PanelGroup(update.view, false, conf.bottomContainer);
 | |
|         }
 | |
|         this.top.syncClasses();
 | |
|         this.bottom.syncClasses();
 | |
|         let input = update.state.facet(showPanel);
 | |
|         if (input != this.input) {
 | |
|             let specs = input.filter(x => x);
 | |
|             let panels = [], top = [], bottom = [], mount = [];
 | |
|             for (let spec of specs) {
 | |
|                 let known = this.specs.indexOf(spec), panel;
 | |
|                 if (known < 0) {
 | |
|                     panel = spec(update.view);
 | |
|                     mount.push(panel);
 | |
|                 }
 | |
|                 else {
 | |
|                     panel = this.panels[known];
 | |
|                     if (panel.update)
 | |
|                         panel.update(update);
 | |
|                 }
 | |
|                 panels.push(panel);
 | |
|                 (panel.top ? top : bottom).push(panel);
 | |
|             }
 | |
|             this.specs = specs;
 | |
|             this.panels = panels;
 | |
|             this.top.sync(top);
 | |
|             this.bottom.sync(bottom);
 | |
|             for (let p of mount) {
 | |
|                 p.dom.classList.add("cm-panel");
 | |
|                 if (p.mount)
 | |
|                     p.mount();
 | |
|             }
 | |
|         }
 | |
|         else {
 | |
|             for (let p of this.panels)
 | |
|                 if (p.update)
 | |
|                     p.update(update);
 | |
|         }
 | |
|     }
 | |
|     destroy() {
 | |
|         this.top.sync([]);
 | |
|         this.bottom.sync([]);
 | |
|     }
 | |
| }, {
 | |
|     provide: plugin => EditorView.scrollMargins.of(view => {
 | |
|         let value = view.plugin(plugin);
 | |
|         return value && { top: value.top.scrollMargin(), bottom: value.bottom.scrollMargin() };
 | |
|     })
 | |
| });
 | |
| class PanelGroup {
 | |
|     constructor(view, top, container) {
 | |
|         this.view = view;
 | |
|         this.top = top;
 | |
|         this.container = container;
 | |
|         this.dom = undefined;
 | |
|         this.classes = "";
 | |
|         this.panels = [];
 | |
|         this.syncClasses();
 | |
|     }
 | |
|     sync(panels) {
 | |
|         for (let p of this.panels)
 | |
|             if (p.destroy && panels.indexOf(p) < 0)
 | |
|                 p.destroy();
 | |
|         this.panels = panels;
 | |
|         this.syncDOM();
 | |
|     }
 | |
|     syncDOM() {
 | |
|         if (this.panels.length == 0) {
 | |
|             if (this.dom) {
 | |
|                 this.dom.remove();
 | |
|                 this.dom = undefined;
 | |
|             }
 | |
|             return;
 | |
|         }
 | |
|         if (!this.dom) {
 | |
|             this.dom = document.createElement("div");
 | |
|             this.dom.className = this.top ? "cm-panels cm-panels-top" : "cm-panels cm-panels-bottom";
 | |
|             this.dom.style[this.top ? "top" : "bottom"] = "0";
 | |
|             let parent = this.container || this.view.dom;
 | |
|             parent.insertBefore(this.dom, this.top ? parent.firstChild : null);
 | |
|         }
 | |
|         let curDOM = this.dom.firstChild;
 | |
|         for (let panel of this.panels) {
 | |
|             if (panel.dom.parentNode == this.dom) {
 | |
|                 while (curDOM != panel.dom)
 | |
|                     curDOM = rm(curDOM);
 | |
|                 curDOM = curDOM.nextSibling;
 | |
|             }
 | |
|             else {
 | |
|                 this.dom.insertBefore(panel.dom, curDOM);
 | |
|             }
 | |
|         }
 | |
|         while (curDOM)
 | |
|             curDOM = rm(curDOM);
 | |
|     }
 | |
|     scrollMargin() {
 | |
|         return !this.dom || this.container ? 0
 | |
|             : Math.max(0, this.top ?
 | |
|                 this.dom.getBoundingClientRect().bottom - Math.max(0, this.view.scrollDOM.getBoundingClientRect().top) :
 | |
|                 Math.min(innerHeight, this.view.scrollDOM.getBoundingClientRect().bottom) - this.dom.getBoundingClientRect().top);
 | |
|     }
 | |
|     syncClasses() {
 | |
|         if (!this.container || this.classes == this.view.themeClasses)
 | |
|             return;
 | |
|         for (let cls of this.classes.split(" "))
 | |
|             if (cls)
 | |
|                 this.container.classList.remove(cls);
 | |
|         for (let cls of (this.classes = this.view.themeClasses).split(" "))
 | |
|             if (cls)
 | |
|                 this.container.classList.add(cls);
 | |
|     }
 | |
| }
 | |
| function rm(node) {
 | |
|     let next = node.nextSibling;
 | |
|     node.remove();
 | |
|     return next;
 | |
| }
 | |
| /**
 | |
| Opening a panel is done by providing a constructor function for
 | |
| the panel through this facet. (The panel is closed again when its
 | |
| constructor is no longer provided.) Values of `null` are ignored.
 | |
| */
 | |
| const showPanel = /*@__PURE__*/Facet.define({
 | |
|     enables: panelPlugin
 | |
| });
 | |
| 
 | |
| /**
 | |
| A gutter marker represents a bit of information attached to a line
 | |
| in a specific gutter. Your own custom markers have to extend this
 | |
| class.
 | |
| */
 | |
| class GutterMarker extends RangeValue {
 | |
|     /**
 | |
|     @internal
 | |
|     */
 | |
|     compare(other) {
 | |
|         return this == other || this.constructor == other.constructor && this.eq(other);
 | |
|     }
 | |
|     /**
 | |
|     Compare this marker to another marker of the same type.
 | |
|     */
 | |
|     eq(other) { return false; }
 | |
|     /**
 | |
|     Called if the marker has a `toDOM` method and its representation
 | |
|     was removed from a gutter.
 | |
|     */
 | |
|     destroy(dom) { }
 | |
| }
 | |
| GutterMarker.prototype.elementClass = "";
 | |
| GutterMarker.prototype.toDOM = undefined;
 | |
| GutterMarker.prototype.mapMode = MapMode.TrackBefore;
 | |
| GutterMarker.prototype.startSide = GutterMarker.prototype.endSide = -1;
 | |
| GutterMarker.prototype.point = true;
 | |
| /**
 | |
| Facet used to add a class to all gutter elements for a given line.
 | |
| Markers given to this facet should _only_ define an
 | |
| [`elementclass`](https://codemirror.net/6/docs/ref/#view.GutterMarker.elementClass), not a
 | |
| [`toDOM`](https://codemirror.net/6/docs/ref/#view.GutterMarker.toDOM) (or the marker will appear
 | |
| in all gutters for the line).
 | |
| */
 | |
| const gutterLineClass = /*@__PURE__*/Facet.define();
 | |
| /**
 | |
| Facet used to add a class to all gutter elements next to a widget.
 | |
| Should not provide widgets with a `toDOM` method.
 | |
| */
 | |
| const gutterWidgetClass = /*@__PURE__*/Facet.define();
 | |
| const defaults = {
 | |
|     class: "",
 | |
|     renderEmptyElements: false,
 | |
|     elementStyle: "",
 | |
|     markers: () => RangeSet.empty,
 | |
|     lineMarker: () => null,
 | |
|     widgetMarker: () => null,
 | |
|     lineMarkerChange: null,
 | |
|     initialSpacer: null,
 | |
|     updateSpacer: null,
 | |
|     domEventHandlers: {}
 | |
| };
 | |
| const activeGutters = /*@__PURE__*/Facet.define();
 | |
| /**
 | |
| Define an editor gutter. The order in which the gutters appear is
 | |
| determined by their extension priority.
 | |
| */
 | |
| function gutter(config) {
 | |
|     return [gutters(), activeGutters.of(Object.assign(Object.assign({}, defaults), config))];
 | |
| }
 | |
| const unfixGutters = /*@__PURE__*/Facet.define({
 | |
|     combine: values => values.some(x => x)
 | |
| });
 | |
| /**
 | |
| The gutter-drawing plugin is automatically enabled when you add a
 | |
| gutter, but you can use this function to explicitly configure it.
 | |
| 
 | |
| Unless `fixed` is explicitly set to `false`, the gutters are
 | |
| fixed, meaning they don't scroll along with the content
 | |
| horizontally (except on Internet Explorer, which doesn't support
 | |
| CSS [`position:
 | |
| sticky`](https://developer.mozilla.org/en-US/docs/Web/CSS/position#sticky)).
 | |
| */
 | |
| function gutters(config) {
 | |
|     let result = [
 | |
|         gutterView,
 | |
|     ];
 | |
|     if (config && config.fixed === false)
 | |
|         result.push(unfixGutters.of(true));
 | |
|     return result;
 | |
| }
 | |
| const gutterView = /*@__PURE__*/ViewPlugin.fromClass(class {
 | |
|     constructor(view) {
 | |
|         this.view = view;
 | |
|         this.prevViewport = view.viewport;
 | |
|         this.dom = document.createElement("div");
 | |
|         this.dom.className = "cm-gutters";
 | |
|         this.dom.setAttribute("aria-hidden", "true");
 | |
|         this.dom.style.minHeight = (this.view.contentHeight / this.view.scaleY) + "px";
 | |
|         this.gutters = view.state.facet(activeGutters).map(conf => new SingleGutterView(view, conf));
 | |
|         for (let gutter of this.gutters)
 | |
|             this.dom.appendChild(gutter.dom);
 | |
|         this.fixed = !view.state.facet(unfixGutters);
 | |
|         if (this.fixed) {
 | |
|             // FIXME IE11 fallback, which doesn't support position: sticky,
 | |
|             // by using position: relative + event handlers that realign the
 | |
|             // gutter (or just force fixed=false on IE11?)
 | |
|             this.dom.style.position = "sticky";
 | |
|         }
 | |
|         this.syncGutters(false);
 | |
|         view.scrollDOM.insertBefore(this.dom, view.contentDOM);
 | |
|     }
 | |
|     update(update) {
 | |
|         if (this.updateGutters(update)) {
 | |
|             // Detach during sync when the viewport changed significantly
 | |
|             // (such as during scrolling), since for large updates that is
 | |
|             // faster.
 | |
|             let vpA = this.prevViewport, vpB = update.view.viewport;
 | |
|             let vpOverlap = Math.min(vpA.to, vpB.to) - Math.max(vpA.from, vpB.from);
 | |
|             this.syncGutters(vpOverlap < (vpB.to - vpB.from) * 0.8);
 | |
|         }
 | |
|         if (update.geometryChanged) {
 | |
|             this.dom.style.minHeight = (this.view.contentHeight / this.view.scaleY) + "px";
 | |
|         }
 | |
|         if (this.view.state.facet(unfixGutters) != !this.fixed) {
 | |
|             this.fixed = !this.fixed;
 | |
|             this.dom.style.position = this.fixed ? "sticky" : "";
 | |
|         }
 | |
|         this.prevViewport = update.view.viewport;
 | |
|     }
 | |
|     syncGutters(detach) {
 | |
|         let after = this.dom.nextSibling;
 | |
|         if (detach)
 | |
|             this.dom.remove();
 | |
|         let lineClasses = RangeSet.iter(this.view.state.facet(gutterLineClass), this.view.viewport.from);
 | |
|         let classSet = [];
 | |
|         let contexts = this.gutters.map(gutter => new UpdateContext(gutter, this.view.viewport, -this.view.documentPadding.top));
 | |
|         for (let line of this.view.viewportLineBlocks) {
 | |
|             if (classSet.length)
 | |
|                 classSet = [];
 | |
|             if (Array.isArray(line.type)) {
 | |
|                 let first = true;
 | |
|                 for (let b of line.type) {
 | |
|                     if (b.type == BlockType.Text && first) {
 | |
|                         advanceCursor(lineClasses, classSet, b.from);
 | |
|                         for (let cx of contexts)
 | |
|                             cx.line(this.view, b, classSet);
 | |
|                         first = false;
 | |
|                     }
 | |
|                     else if (b.widget) {
 | |
|                         for (let cx of contexts)
 | |
|                             cx.widget(this.view, b);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             else if (line.type == BlockType.Text) {
 | |
|                 advanceCursor(lineClasses, classSet, line.from);
 | |
|                 for (let cx of contexts)
 | |
|                     cx.line(this.view, line, classSet);
 | |
|             }
 | |
|             else if (line.widget) {
 | |
|                 for (let cx of contexts)
 | |
|                     cx.widget(this.view, line);
 | |
|             }
 | |
|         }
 | |
|         for (let cx of contexts)
 | |
|             cx.finish();
 | |
|         if (detach)
 | |
|             this.view.scrollDOM.insertBefore(this.dom, after);
 | |
|     }
 | |
|     updateGutters(update) {
 | |
|         let prev = update.startState.facet(activeGutters), cur = update.state.facet(activeGutters);
 | |
|         let change = update.docChanged || update.heightChanged || update.viewportChanged ||
 | |
|             !RangeSet.eq(update.startState.facet(gutterLineClass), update.state.facet(gutterLineClass), update.view.viewport.from, update.view.viewport.to);
 | |
|         if (prev == cur) {
 | |
|             for (let gutter of this.gutters)
 | |
|                 if (gutter.update(update))
 | |
|                     change = true;
 | |
|         }
 | |
|         else {
 | |
|             change = true;
 | |
|             let gutters = [];
 | |
|             for (let conf of cur) {
 | |
|                 let known = prev.indexOf(conf);
 | |
|                 if (known < 0) {
 | |
|                     gutters.push(new SingleGutterView(this.view, conf));
 | |
|                 }
 | |
|                 else {
 | |
|                     this.gutters[known].update(update);
 | |
|                     gutters.push(this.gutters[known]);
 | |
|                 }
 | |
|             }
 | |
|             for (let g of this.gutters) {
 | |
|                 g.dom.remove();
 | |
|                 if (gutters.indexOf(g) < 0)
 | |
|                     g.destroy();
 | |
|             }
 | |
|             for (let g of gutters)
 | |
|                 this.dom.appendChild(g.dom);
 | |
|             this.gutters = gutters;
 | |
|         }
 | |
|         return change;
 | |
|     }
 | |
|     destroy() {
 | |
|         for (let view of this.gutters)
 | |
|             view.destroy();
 | |
|         this.dom.remove();
 | |
|     }
 | |
| }, {
 | |
|     provide: plugin => EditorView.scrollMargins.of(view => {
 | |
|         let value = view.plugin(plugin);
 | |
|         if (!value || value.gutters.length == 0 || !value.fixed)
 | |
|             return null;
 | |
|         return view.textDirection == Direction.LTR
 | |
|             ? { left: value.dom.offsetWidth * view.scaleX }
 | |
|             : { right: value.dom.offsetWidth * view.scaleX };
 | |
|     })
 | |
| });
 | |
| function asArray(val) { return (Array.isArray(val) ? val : [val]); }
 | |
| function advanceCursor(cursor, collect, pos) {
 | |
|     while (cursor.value && cursor.from <= pos) {
 | |
|         if (cursor.from == pos)
 | |
|             collect.push(cursor.value);
 | |
|         cursor.next();
 | |
|     }
 | |
| }
 | |
| class UpdateContext {
 | |
|     constructor(gutter, viewport, height) {
 | |
|         this.gutter = gutter;
 | |
|         this.height = height;
 | |
|         this.i = 0;
 | |
|         this.cursor = RangeSet.iter(gutter.markers, viewport.from);
 | |
|     }
 | |
|     addElement(view, block, markers) {
 | |
|         let { gutter } = this, above = (block.top - this.height) / view.scaleY, height = block.height / view.scaleY;
 | |
|         if (this.i == gutter.elements.length) {
 | |
|             let newElt = new GutterElement(view, height, above, markers);
 | |
|             gutter.elements.push(newElt);
 | |
|             gutter.dom.appendChild(newElt.dom);
 | |
|         }
 | |
|         else {
 | |
|             gutter.elements[this.i].update(view, height, above, markers);
 | |
|         }
 | |
|         this.height = block.bottom;
 | |
|         this.i++;
 | |
|     }
 | |
|     line(view, line, extraMarkers) {
 | |
|         let localMarkers = [];
 | |
|         advanceCursor(this.cursor, localMarkers, line.from);
 | |
|         if (extraMarkers.length)
 | |
|             localMarkers = localMarkers.concat(extraMarkers);
 | |
|         let forLine = this.gutter.config.lineMarker(view, line, localMarkers);
 | |
|         if (forLine)
 | |
|             localMarkers.unshift(forLine);
 | |
|         let gutter = this.gutter;
 | |
|         if (localMarkers.length == 0 && !gutter.config.renderEmptyElements)
 | |
|             return;
 | |
|         this.addElement(view, line, localMarkers);
 | |
|     }
 | |
|     widget(view, block) {
 | |
|         let marker = this.gutter.config.widgetMarker(view, block.widget, block), markers = marker ? [marker] : null;
 | |
|         for (let cls of view.state.facet(gutterWidgetClass)) {
 | |
|             let marker = cls(view, block.widget, block);
 | |
|             if (marker)
 | |
|                 (markers || (markers = [])).push(marker);
 | |
|         }
 | |
|         if (markers)
 | |
|             this.addElement(view, block, markers);
 | |
|     }
 | |
|     finish() {
 | |
|         let gutter = this.gutter;
 | |
|         while (gutter.elements.length > this.i) {
 | |
|             let last = gutter.elements.pop();
 | |
|             gutter.dom.removeChild(last.dom);
 | |
|             last.destroy();
 | |
|         }
 | |
|     }
 | |
| }
 | |
| class SingleGutterView {
 | |
|     constructor(view, config) {
 | |
|         this.view = view;
 | |
|         this.config = config;
 | |
|         this.elements = [];
 | |
|         this.spacer = null;
 | |
|         this.dom = document.createElement("div");
 | |
|         this.dom.className = "cm-gutter" + (this.config.class ? " " + this.config.class : "");
 | |
|         for (let prop in config.domEventHandlers) {
 | |
|             this.dom.addEventListener(prop, (event) => {
 | |
|                 let target = event.target, y;
 | |
|                 if (target != this.dom && this.dom.contains(target)) {
 | |
|                     while (target.parentNode != this.dom)
 | |
|                         target = target.parentNode;
 | |
|                     let rect = target.getBoundingClientRect();
 | |
|                     y = (rect.top + rect.bottom) / 2;
 | |
|                 }
 | |
|                 else {
 | |
|                     y = event.clientY;
 | |
|                 }
 | |
|                 let line = view.lineBlockAtHeight(y - view.documentTop);
 | |
|                 if (config.domEventHandlers[prop](view, line, event))
 | |
|                     event.preventDefault();
 | |
|             });
 | |
|         }
 | |
|         this.markers = asArray(config.markers(view));
 | |
|         if (config.initialSpacer) {
 | |
|             this.spacer = new GutterElement(view, 0, 0, [config.initialSpacer(view)]);
 | |
|             this.dom.appendChild(this.spacer.dom);
 | |
|             this.spacer.dom.style.cssText += "visibility: hidden; pointer-events: none";
 | |
|         }
 | |
|     }
 | |
|     update(update) {
 | |
|         let prevMarkers = this.markers;
 | |
|         this.markers = asArray(this.config.markers(update.view));
 | |
|         if (this.spacer && this.config.updateSpacer) {
 | |
|             let updated = this.config.updateSpacer(this.spacer.markers[0], update);
 | |
|             if (updated != this.spacer.markers[0])
 | |
|                 this.spacer.update(update.view, 0, 0, [updated]);
 | |
|         }
 | |
|         let vp = update.view.viewport;
 | |
|         return !RangeSet.eq(this.markers, prevMarkers, vp.from, vp.to) ||
 | |
|             (this.config.lineMarkerChange ? this.config.lineMarkerChange(update) : false);
 | |
|     }
 | |
|     destroy() {
 | |
|         for (let elt of this.elements)
 | |
|             elt.destroy();
 | |
|     }
 | |
| }
 | |
| class GutterElement {
 | |
|     constructor(view, height, above, markers) {
 | |
|         this.height = -1;
 | |
|         this.above = 0;
 | |
|         this.markers = [];
 | |
|         this.dom = document.createElement("div");
 | |
|         this.dom.className = "cm-gutterElement";
 | |
|         this.update(view, height, above, markers);
 | |
|     }
 | |
|     update(view, height, above, markers) {
 | |
|         if (this.height != height) {
 | |
|             this.height = height;
 | |
|             this.dom.style.height = height + "px";
 | |
|         }
 | |
|         if (this.above != above)
 | |
|             this.dom.style.marginTop = (this.above = above) ? above + "px" : "";
 | |
|         if (!sameMarkers(this.markers, markers))
 | |
|             this.setMarkers(view, markers);
 | |
|     }
 | |
|     setMarkers(view, markers) {
 | |
|         let cls = "cm-gutterElement", domPos = this.dom.firstChild;
 | |
|         for (let iNew = 0, iOld = 0;;) {
 | |
|             let skipTo = iOld, marker = iNew < markers.length ? markers[iNew++] : null, matched = false;
 | |
|             if (marker) {
 | |
|                 let c = marker.elementClass;
 | |
|                 if (c)
 | |
|                     cls += " " + c;
 | |
|                 for (let i = iOld; i < this.markers.length; i++)
 | |
|                     if (this.markers[i].compare(marker)) {
 | |
|                         skipTo = i;
 | |
|                         matched = true;
 | |
|                         break;
 | |
|                     }
 | |
|             }
 | |
|             else {
 | |
|                 skipTo = this.markers.length;
 | |
|             }
 | |
|             while (iOld < skipTo) {
 | |
|                 let next = this.markers[iOld++];
 | |
|                 if (next.toDOM) {
 | |
|                     next.destroy(domPos);
 | |
|                     let after = domPos.nextSibling;
 | |
|                     domPos.remove();
 | |
|                     domPos = after;
 | |
|                 }
 | |
|             }
 | |
|             if (!marker)
 | |
|                 break;
 | |
|             if (marker.toDOM) {
 | |
|                 if (matched)
 | |
|                     domPos = domPos.nextSibling;
 | |
|                 else
 | |
|                     this.dom.insertBefore(marker.toDOM(view), domPos);
 | |
|             }
 | |
|             if (matched)
 | |
|                 iOld++;
 | |
|         }
 | |
|         this.dom.className = cls;
 | |
|         this.markers = markers;
 | |
|     }
 | |
|     destroy() {
 | |
|         this.setMarkers(null, []); // First argument not used unless creating markers
 | |
|     }
 | |
| }
 | |
| function sameMarkers(a, b) {
 | |
|     if (a.length != b.length)
 | |
|         return false;
 | |
|     for (let i = 0; i < a.length; i++)
 | |
|         if (!a[i].compare(b[i]))
 | |
|             return false;
 | |
|     return true;
 | |
| }
 | |
| /**
 | |
| Facet used to provide markers to the line number gutter.
 | |
| */
 | |
| const lineNumberMarkers = /*@__PURE__*/Facet.define();
 | |
| /**
 | |
| Facet used to create markers in the line number gutter next to widgets.
 | |
| */
 | |
| const lineNumberWidgetMarker = /*@__PURE__*/Facet.define();
 | |
| const lineNumberConfig = /*@__PURE__*/Facet.define({
 | |
|     combine(values) {
 | |
|         return combineConfig(values, { formatNumber: String, domEventHandlers: {} }, {
 | |
|             domEventHandlers(a, b) {
 | |
|                 let result = Object.assign({}, a);
 | |
|                 for (let event in b) {
 | |
|                     let exists = result[event], add = b[event];
 | |
|                     result[event] = exists ? (view, line, event) => exists(view, line, event) || add(view, line, event) : add;
 | |
|                 }
 | |
|                 return result;
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| });
 | |
| class NumberMarker extends GutterMarker {
 | |
|     constructor(number) {
 | |
|         super();
 | |
|         this.number = number;
 | |
|     }
 | |
|     eq(other) { return this.number == other.number; }
 | |
|     toDOM() { return document.createTextNode(this.number); }
 | |
| }
 | |
| function formatNumber(view, number) {
 | |
|     return view.state.facet(lineNumberConfig).formatNumber(number, view.state);
 | |
| }
 | |
| const lineNumberGutter = /*@__PURE__*/activeGutters.compute([lineNumberConfig], state => ({
 | |
|     class: "cm-lineNumbers",
 | |
|     renderEmptyElements: false,
 | |
|     markers(view) { return view.state.facet(lineNumberMarkers); },
 | |
|     lineMarker(view, line, others) {
 | |
|         if (others.some(m => m.toDOM))
 | |
|             return null;
 | |
|         return new NumberMarker(formatNumber(view, view.state.doc.lineAt(line.from).number));
 | |
|     },
 | |
|     widgetMarker: (view, widget, block) => {
 | |
|         for (let m of view.state.facet(lineNumberWidgetMarker)) {
 | |
|             let result = m(view, widget, block);
 | |
|             if (result)
 | |
|                 return result;
 | |
|         }
 | |
|         return null;
 | |
|     },
 | |
|     lineMarkerChange: update => update.startState.facet(lineNumberConfig) != update.state.facet(lineNumberConfig),
 | |
|     initialSpacer(view) {
 | |
|         return new NumberMarker(formatNumber(view, maxLineNumber(view.state.doc.lines)));
 | |
|     },
 | |
|     updateSpacer(spacer, update) {
 | |
|         let max = formatNumber(update.view, maxLineNumber(update.view.state.doc.lines));
 | |
|         return max == spacer.number ? spacer : new NumberMarker(max);
 | |
|     },
 | |
|     domEventHandlers: state.facet(lineNumberConfig).domEventHandlers
 | |
| }));
 | |
| /**
 | |
| Create a line number gutter extension.
 | |
| */
 | |
| function lineNumbers(config = {}) {
 | |
|     return [
 | |
|         lineNumberConfig.of(config),
 | |
|         gutters(),
 | |
|         lineNumberGutter
 | |
|     ];
 | |
| }
 | |
| function maxLineNumber(lines) {
 | |
|     let last = 9;
 | |
|     while (last < lines)
 | |
|         last = last * 10 + 9;
 | |
|     return last;
 | |
| }
 | |
| const activeLineGutterMarker = /*@__PURE__*/new class extends GutterMarker {
 | |
|     constructor() {
 | |
|         super(...arguments);
 | |
|         this.elementClass = "cm-activeLineGutter";
 | |
|     }
 | |
| };
 | |
| const activeLineGutterHighlighter = /*@__PURE__*/gutterLineClass.compute(["selection"], state => {
 | |
|     let marks = [], last = -1;
 | |
|     for (let range of state.selection.ranges) {
 | |
|         let linePos = state.doc.lineAt(range.head).from;
 | |
|         if (linePos > last) {
 | |
|             last = linePos;
 | |
|             marks.push(activeLineGutterMarker.range(linePos));
 | |
|         }
 | |
|     }
 | |
|     return RangeSet.of(marks);
 | |
| });
 | |
| /**
 | |
| Returns an extension that adds a `cm-activeLineGutter` class to
 | |
| all gutter elements on the [active
 | |
| line](https://codemirror.net/6/docs/ref/#view.highlightActiveLine).
 | |
| */
 | |
| function highlightActiveLineGutter() {
 | |
|     return activeLineGutterHighlighter;
 | |
| }
 | |
| 
 | |
| const WhitespaceDeco = /*@__PURE__*/new Map();
 | |
| function getWhitespaceDeco(space) {
 | |
|     let deco = WhitespaceDeco.get(space);
 | |
|     if (!deco)
 | |
|         WhitespaceDeco.set(space, deco = Decoration.mark({
 | |
|             attributes: space === "\t" ? {
 | |
|                 class: "cm-highlightTab",
 | |
|             } : {
 | |
|                 class: "cm-highlightSpace",
 | |
|                 "data-display": space.replace(/ /g, "·")
 | |
|             }
 | |
|         }));
 | |
|     return deco;
 | |
| }
 | |
| function matcher(decorator) {
 | |
|     return ViewPlugin.define(view => ({
 | |
|         decorations: decorator.createDeco(view),
 | |
|         update(u) {
 | |
|             this.decorations = decorator.updateDeco(u, this.decorations);
 | |
|         },
 | |
|     }), {
 | |
|         decorations: v => v.decorations
 | |
|     });
 | |
| }
 | |
| const whitespaceHighlighter = /*@__PURE__*/matcher(/*@__PURE__*/new MatchDecorator({
 | |
|     regexp: /\t| +/g,
 | |
|     decoration: match => getWhitespaceDeco(match[0]),
 | |
|     boundary: /\S/,
 | |
| }));
 | |
| /**
 | |
| Returns an extension that highlights whitespace, adding a
 | |
| `cm-highlightSpace` class to stretches of spaces, and a
 | |
| `cm-highlightTab` class to individual tab characters. By default,
 | |
| the former are shown as faint dots, and the latter as arrows.
 | |
| */
 | |
| function highlightWhitespace() {
 | |
|     return whitespaceHighlighter;
 | |
| }
 | |
| const trailingHighlighter = /*@__PURE__*/matcher(/*@__PURE__*/new MatchDecorator({
 | |
|     regexp: /\s+$/g,
 | |
|     decoration: /*@__PURE__*/Decoration.mark({ class: "cm-trailingSpace" }),
 | |
|     boundary: /\S/,
 | |
| }));
 | |
| /**
 | |
| Returns an extension that adds a `cm-trailingSpace` class to all
 | |
| trailing whitespace.
 | |
| */
 | |
| function highlightTrailingWhitespace() {
 | |
|     return trailingHighlighter;
 | |
| }
 | |
| 
 | |
| /**
 | |
| @internal
 | |
| */
 | |
| const __test = { HeightMap, HeightOracle, MeasuredHeights, QueryType, ChangedRange, computeOrder,
 | |
|     moveVisually, clearHeightChangeFlag, getHeightChangeFlag: () => heightChangeFlag };
 | |
| 
 | |
| export { BidiSpan, BlockInfo, BlockType, Decoration, Direction, EditorView, GutterMarker, MatchDecorator, RectangleMarker, ViewPlugin, ViewUpdate, WidgetType, __test, closeHoverTooltips, crosshairCursor, drawSelection, dropCursor, getDrawSelectionConfig, getPanel, getTooltip, gutter, gutterLineClass, gutterWidgetClass, gutters, hasHoverTooltips, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, highlightTrailingWhitespace, highlightWhitespace, hoverTooltip, keymap, layer, lineNumberMarkers, lineNumberWidgetMarker, lineNumbers, logException, panels, placeholder, rectangularSelection, repositionTooltips, runScopeHandlers, scrollPastEnd, showPanel, showTooltip, tooltips };
 |