UNPKG

361 kBJavaScriptView Raw
1import { Text, RangeSet, MapMode, RangeValue, Facet, StateEffect, ChangeSet, findClusterBreak, EditorSelection, EditorState, findColumn, CharCategory, Prec, Transaction, codePointAt, codePointSize, combineConfig, StateField, RangeSetBuilder, countColumn } from '@codemirror/state';
2import { StyleModule } from 'style-mod';
3import { keyName, base, shift } from 'w3c-keyname';
4
5function getSelection(root) {
6 let target;
7 // Browsers differ on whether shadow roots have a getSelection
8 // method. If it exists, use that, otherwise, call it on the
9 // document.
10 if (root.nodeType == 11) { // Shadow root
11 target = root.getSelection ? root : root.ownerDocument;
12 }
13 else {
14 target = root;
15 }
16 return target.getSelection();
17}
18function contains(dom, node) {
19 return node ? dom == node || dom.contains(node.nodeType != 1 ? node.parentNode : node) : false;
20}
21function deepActiveElement() {
22 let elt = document.activeElement;
23 while (elt && elt.shadowRoot)
24 elt = elt.shadowRoot.activeElement;
25 return elt;
26}
27function hasSelection(dom, selection) {
28 if (!selection.anchorNode)
29 return false;
30 try {
31 // Firefox will raise 'permission denied' errors when accessing
32 // properties of `sel.anchorNode` when it's in a generated CSS
33 // element.
34 return contains(dom, selection.anchorNode);
35 }
36 catch (_) {
37 return false;
38 }
39}
40function clientRectsFor(dom) {
41 if (dom.nodeType == 3)
42 return textRange(dom, 0, dom.nodeValue.length).getClientRects();
43 else if (dom.nodeType == 1)
44 return dom.getClientRects();
45 else
46 return [];
47}
48// Scans forward and backward through DOM positions equivalent to the
49// given one to see if the two are in the same place (i.e. after a
50// text node vs at the end of that text node)
51function isEquivalentPosition(node, off, targetNode, targetOff) {
52 return targetNode ? (scanFor(node, off, targetNode, targetOff, -1) ||
53 scanFor(node, off, targetNode, targetOff, 1)) : false;
54}
55function domIndex(node) {
56 for (var index = 0;; index++) {
57 node = node.previousSibling;
58 if (!node)
59 return index;
60 }
61}
62function scanFor(node, off, targetNode, targetOff, dir) {
63 for (;;) {
64 if (node == targetNode && off == targetOff)
65 return true;
66 if (off == (dir < 0 ? 0 : maxOffset(node))) {
67 if (node.nodeName == "DIV")
68 return false;
69 let parent = node.parentNode;
70 if (!parent || parent.nodeType != 1)
71 return false;
72 off = domIndex(node) + (dir < 0 ? 0 : 1);
73 node = parent;
74 }
75 else if (node.nodeType == 1) {
76 node = node.childNodes[off + (dir < 0 ? -1 : 0)];
77 if (node.nodeType == 1 && node.contentEditable == "false")
78 return false;
79 off = dir < 0 ? maxOffset(node) : 0;
80 }
81 else {
82 return false;
83 }
84 }
85}
86function maxOffset(node) {
87 return node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length;
88}
89const Rect0 = { left: 0, right: 0, top: 0, bottom: 0 };
90function flattenRect(rect, left) {
91 let x = left ? rect.left : rect.right;
92 return { left: x, right: x, top: rect.top, bottom: rect.bottom };
93}
94function windowRect(win) {
95 return { left: 0, right: win.innerWidth,
96 top: 0, bottom: win.innerHeight };
97}
98function scrollRectIntoView(dom, rect, side, x, y, xMargin, yMargin, ltr) {
99 let doc = dom.ownerDocument, win = doc.defaultView;
100 for (let cur = dom; cur;) {
101 if (cur.nodeType == 1) { // Element
102 let bounding, top = cur == doc.body;
103 if (top) {
104 bounding = windowRect(win);
105 }
106 else {
107 if (cur.scrollHeight <= cur.clientHeight && cur.scrollWidth <= cur.clientWidth) {
108 cur = cur.parentNode;
109 continue;
110 }
111 let rect = cur.getBoundingClientRect();
112 // Make sure scrollbar width isn't included in the rectangle
113 bounding = { left: rect.left, right: rect.left + cur.clientWidth,
114 top: rect.top, bottom: rect.top + cur.clientHeight };
115 }
116 let moveX = 0, moveY = 0;
117 if (y == "nearest") {
118 if (rect.top < bounding.top) {
119 moveY = -(bounding.top - rect.top + yMargin);
120 if (side > 0 && rect.bottom > bounding.bottom + moveY)
121 moveY = rect.bottom - bounding.bottom + moveY + yMargin;
122 }
123 else if (rect.bottom > bounding.bottom) {
124 moveY = rect.bottom - bounding.bottom + yMargin;
125 if (side < 0 && (rect.top - moveY) < bounding.top)
126 moveY = -(bounding.top + moveY - rect.top + yMargin);
127 }
128 }
129 else {
130 let rectHeight = rect.bottom - rect.top, boundingHeight = bounding.bottom - bounding.top;
131 let targetTop = y == "center" && rectHeight <= boundingHeight ? rect.top + rectHeight / 2 - boundingHeight / 2 :
132 y == "start" || y == "center" && side < 0 ? rect.top - yMargin :
133 rect.bottom - boundingHeight + yMargin;
134 moveY = targetTop - bounding.top;
135 }
136 if (x == "nearest") {
137 if (rect.left < bounding.left) {
138 moveX = -(bounding.left - rect.left + xMargin);
139 if (side > 0 && rect.right > bounding.right + moveX)
140 moveX = rect.right - bounding.right + moveX + xMargin;
141 }
142 else if (rect.right > bounding.right) {
143 moveX = rect.right - bounding.right + xMargin;
144 if (side < 0 && rect.left < bounding.left + moveX)
145 moveX = -(bounding.left + moveX - rect.left + xMargin);
146 }
147 }
148 else {
149 let targetLeft = x == "center" ? rect.left + (rect.right - rect.left) / 2 - (bounding.right - bounding.left) / 2 :
150 (x == "start") == ltr ? rect.left - xMargin :
151 rect.right - (bounding.right - bounding.left) + xMargin;
152 moveX = targetLeft - bounding.left;
153 }
154 if (moveX || moveY) {
155 if (top) {
156 win.scrollBy(moveX, moveY);
157 }
158 else {
159 if (moveY) {
160 let start = cur.scrollTop;
161 cur.scrollTop += moveY;
162 moveY = cur.scrollTop - start;
163 }
164 if (moveX) {
165 let start = cur.scrollLeft;
166 cur.scrollLeft += moveX;
167 moveX = cur.scrollLeft - start;
168 }
169 rect = { left: rect.left - moveX, top: rect.top - moveY,
170 right: rect.right - moveX, bottom: rect.bottom - moveY };
171 }
172 }
173 if (top)
174 break;
175 cur = cur.assignedSlot || cur.parentNode;
176 x = y = "nearest";
177 }
178 else if (cur.nodeType == 11) { // A shadow root
179 cur = cur.host;
180 }
181 else {
182 break;
183 }
184 }
185}
186class DOMSelectionState {
187 constructor() {
188 this.anchorNode = null;
189 this.anchorOffset = 0;
190 this.focusNode = null;
191 this.focusOffset = 0;
192 }
193 eq(domSel) {
194 return this.anchorNode == domSel.anchorNode && this.anchorOffset == domSel.anchorOffset &&
195 this.focusNode == domSel.focusNode && this.focusOffset == domSel.focusOffset;
196 }
197 setRange(range) {
198 this.set(range.anchorNode, range.anchorOffset, range.focusNode, range.focusOffset);
199 }
200 set(anchorNode, anchorOffset, focusNode, focusOffset) {
201 this.anchorNode = anchorNode;
202 this.anchorOffset = anchorOffset;
203 this.focusNode = focusNode;
204 this.focusOffset = focusOffset;
205 }
206}
207let preventScrollSupported = null;
208// Feature-detects support for .focus({preventScroll: true}), and uses
209// a fallback kludge when not supported.
210function focusPreventScroll(dom) {
211 if (dom.setActive)
212 return dom.setActive(); // in IE
213 if (preventScrollSupported)
214 return dom.focus(preventScrollSupported);
215 let stack = [];
216 for (let cur = dom; cur; cur = cur.parentNode) {
217 stack.push(cur, cur.scrollTop, cur.scrollLeft);
218 if (cur == cur.ownerDocument)
219 break;
220 }
221 dom.focus(preventScrollSupported == null ? {
222 get preventScroll() {
223 preventScrollSupported = { preventScroll: true };
224 return true;
225 }
226 } : undefined);
227 if (!preventScrollSupported) {
228 preventScrollSupported = false;
229 for (let i = 0; i < stack.length;) {
230 let elt = stack[i++], top = stack[i++], left = stack[i++];
231 if (elt.scrollTop != top)
232 elt.scrollTop = top;
233 if (elt.scrollLeft != left)
234 elt.scrollLeft = left;
235 }
236 }
237}
238let scratchRange;
239function textRange(node, from, to = from) {
240 let range = scratchRange || (scratchRange = document.createRange());
241 range.setEnd(node, to);
242 range.setStart(node, from);
243 return range;
244}
245function dispatchKey(elt, name, code) {
246 let options = { key: name, code: name, keyCode: code, which: code, cancelable: true };
247 let down = new KeyboardEvent("keydown", options);
248 down.synthetic = true;
249 elt.dispatchEvent(down);
250 let up = new KeyboardEvent("keyup", options);
251 up.synthetic = true;
252 elt.dispatchEvent(up);
253 return down.defaultPrevented || up.defaultPrevented;
254}
255function getRoot(node) {
256 while (node) {
257 if (node && (node.nodeType == 9 || node.nodeType == 11 && node.host))
258 return node;
259 node = node.assignedSlot || node.parentNode;
260 }
261 return null;
262}
263function clearAttributes(node) {
264 while (node.attributes.length)
265 node.removeAttributeNode(node.attributes[0]);
266}
267
268class DOMPos {
269 constructor(node, offset, precise = true) {
270 this.node = node;
271 this.offset = offset;
272 this.precise = precise;
273 }
274 static before(dom, precise) { return new DOMPos(dom.parentNode, domIndex(dom), precise); }
275 static after(dom, precise) { return new DOMPos(dom.parentNode, domIndex(dom) + 1, precise); }
276}
277const noChildren = [];
278class ContentView {
279 constructor() {
280 this.parent = null;
281 this.dom = null;
282 this.dirty = 2 /* Node */;
283 }
284 get editorView() {
285 if (!this.parent)
286 throw new Error("Accessing view in orphan content view");
287 return this.parent.editorView;
288 }
289 get overrideDOMText() { return null; }
290 get posAtStart() {
291 return this.parent ? this.parent.posBefore(this) : 0;
292 }
293 get posAtEnd() {
294 return this.posAtStart + this.length;
295 }
296 posBefore(view) {
297 let pos = this.posAtStart;
298 for (let child of this.children) {
299 if (child == view)
300 return pos;
301 pos += child.length + child.breakAfter;
302 }
303 throw new RangeError("Invalid child in posBefore");
304 }
305 posAfter(view) {
306 return this.posBefore(view) + view.length;
307 }
308 // Will return a rectangle directly before (when side < 0), after
309 // (side > 0) or directly on (when the browser supports it) the
310 // given position.
311 coordsAt(_pos, _side) { return null; }
312 sync(track) {
313 if (this.dirty & 2 /* Node */) {
314 let parent = this.dom;
315 let prev = null, next;
316 for (let child of this.children) {
317 if (child.dirty) {
318 if (!child.dom && (next = prev ? prev.nextSibling : parent.firstChild)) {
319 let contentView = ContentView.get(next);
320 if (!contentView || !contentView.parent && contentView.constructor == child.constructor)
321 child.reuseDOM(next);
322 }
323 child.sync(track);
324 child.dirty = 0 /* Not */;
325 }
326 next = prev ? prev.nextSibling : parent.firstChild;
327 if (track && !track.written && track.node == parent && next != child.dom)
328 track.written = true;
329 if (child.dom.parentNode == parent) {
330 while (next && next != child.dom)
331 next = rm$1(next);
332 }
333 else {
334 parent.insertBefore(child.dom, next);
335 }
336 prev = child.dom;
337 }
338 next = prev ? prev.nextSibling : parent.firstChild;
339 if (next && track && track.node == parent)
340 track.written = true;
341 while (next)
342 next = rm$1(next);
343 }
344 else if (this.dirty & 1 /* Child */) {
345 for (let child of this.children)
346 if (child.dirty) {
347 child.sync(track);
348 child.dirty = 0 /* Not */;
349 }
350 }
351 }
352 reuseDOM(_dom) { }
353 localPosFromDOM(node, offset) {
354 let after;
355 if (node == this.dom) {
356 after = this.dom.childNodes[offset];
357 }
358 else {
359 let bias = maxOffset(node) == 0 ? 0 : offset == 0 ? -1 : 1;
360 for (;;) {
361 let parent = node.parentNode;
362 if (parent == this.dom)
363 break;
364 if (bias == 0 && parent.firstChild != parent.lastChild) {
365 if (node == parent.firstChild)
366 bias = -1;
367 else
368 bias = 1;
369 }
370 node = parent;
371 }
372 if (bias < 0)
373 after = node;
374 else
375 after = node.nextSibling;
376 }
377 if (after == this.dom.firstChild)
378 return 0;
379 while (after && !ContentView.get(after))
380 after = after.nextSibling;
381 if (!after)
382 return this.length;
383 for (let i = 0, pos = 0;; i++) {
384 let child = this.children[i];
385 if (child.dom == after)
386 return pos;
387 pos += child.length + child.breakAfter;
388 }
389 }
390 domBoundsAround(from, to, offset = 0) {
391 let fromI = -1, fromStart = -1, toI = -1, toEnd = -1;
392 for (let i = 0, pos = offset, prevEnd = offset; i < this.children.length; i++) {
393 let child = this.children[i], end = pos + child.length;
394 if (pos < from && end > to)
395 return child.domBoundsAround(from, to, pos);
396 if (end >= from && fromI == -1) {
397 fromI = i;
398 fromStart = pos;
399 }
400 if (pos > to && child.dom.parentNode == this.dom) {
401 toI = i;
402 toEnd = prevEnd;
403 break;
404 }
405 prevEnd = end;
406 pos = end + child.breakAfter;
407 }
408 return { from: fromStart, to: toEnd < 0 ? offset + this.length : toEnd,
409 startDOM: (fromI ? this.children[fromI - 1].dom.nextSibling : null) || this.dom.firstChild,
410 endDOM: toI < this.children.length && toI >= 0 ? this.children[toI].dom : null };
411 }
412 markDirty(andParent = false) {
413 this.dirty |= 2 /* Node */;
414 this.markParentsDirty(andParent);
415 }
416 markParentsDirty(childList) {
417 for (let parent = this.parent; parent; parent = parent.parent) {
418 if (childList)
419 parent.dirty |= 2 /* Node */;
420 if (parent.dirty & 1 /* Child */)
421 return;
422 parent.dirty |= 1 /* Child */;
423 childList = false;
424 }
425 }
426 setParent(parent) {
427 if (this.parent != parent) {
428 this.parent = parent;
429 if (this.dirty)
430 this.markParentsDirty(true);
431 }
432 }
433 setDOM(dom) {
434 if (this.dom)
435 this.dom.cmView = null;
436 this.dom = dom;
437 dom.cmView = this;
438 }
439 get rootView() {
440 for (let v = this;;) {
441 let parent = v.parent;
442 if (!parent)
443 return v;
444 v = parent;
445 }
446 }
447 replaceChildren(from, to, children = noChildren) {
448 this.markDirty();
449 for (let i = from; i < to; i++) {
450 let child = this.children[i];
451 if (child.parent == this)
452 child.destroy();
453 }
454 this.children.splice(from, to - from, ...children);
455 for (let i = 0; i < children.length; i++)
456 children[i].setParent(this);
457 }
458 ignoreMutation(_rec) { return false; }
459 ignoreEvent(_event) { return false; }
460 childCursor(pos = this.length) {
461 return new ChildCursor(this.children, pos, this.children.length);
462 }
463 childPos(pos, bias = 1) {
464 return this.childCursor().findPos(pos, bias);
465 }
466 toString() {
467 let name = this.constructor.name.replace("View", "");
468 return name + (this.children.length ? "(" + this.children.join() + ")" :
469 this.length ? "[" + (name == "Text" ? this.text : this.length) + "]" : "") +
470 (this.breakAfter ? "#" : "");
471 }
472 static get(node) { return node.cmView; }
473 get isEditable() { return true; }
474 merge(from, to, source, hasStart, openStart, openEnd) {
475 return false;
476 }
477 become(other) { return false; }
478 // When this is a zero-length view with a side, this should return a
479 // number <= 0 to indicate it is before its position, or a
480 // number > 0 when after its position.
481 getSide() { return 0; }
482 destroy() {
483 this.parent = null;
484 }
485}
486ContentView.prototype.breakAfter = 0;
487// Remove a DOM node and return its next sibling.
488function rm$1(dom) {
489 let next = dom.nextSibling;
490 dom.parentNode.removeChild(dom);
491 return next;
492}
493class ChildCursor {
494 constructor(children, pos, i) {
495 this.children = children;
496 this.pos = pos;
497 this.i = i;
498 this.off = 0;
499 }
500 findPos(pos, bias = 1) {
501 for (;;) {
502 if (pos > this.pos || pos == this.pos &&
503 (bias > 0 || this.i == 0 || this.children[this.i - 1].breakAfter)) {
504 this.off = pos - this.pos;
505 return this;
506 }
507 let next = this.children[--this.i];
508 this.pos -= next.length + next.breakAfter;
509 }
510 }
511}
512function replaceRange(parent, fromI, fromOff, toI, toOff, insert, breakAtStart, openStart, openEnd) {
513 let { children } = parent;
514 let before = children.length ? children[fromI] : null;
515 let last = insert.length ? insert[insert.length - 1] : null;
516 let breakAtEnd = last ? last.breakAfter : breakAtStart;
517 // Change within a single child
518 if (fromI == toI && before && !breakAtStart && !breakAtEnd && insert.length < 2 &&
519 before.merge(fromOff, toOff, insert.length ? last : null, fromOff == 0, openStart, openEnd))
520 return;
521 if (toI < children.length) {
522 let after = children[toI];
523 // Make sure the end of the child after the update is preserved in `after`
524 if (after && toOff < after.length) {
525 // If we're splitting a child, separate part of it to avoid that
526 // being mangled when updating the child before the update.
527 if (fromI == toI) {
528 after = after.split(toOff);
529 toOff = 0;
530 }
531 // If the element after the replacement should be merged with
532 // the last replacing element, update `content`
533 if (!breakAtEnd && last && after.merge(0, toOff, last, true, 0, openEnd)) {
534 insert[insert.length - 1] = after;
535 }
536 else {
537 // Remove the start of the after element, if necessary, and
538 // add it to `content`.
539 if (toOff)
540 after.merge(0, toOff, null, false, 0, openEnd);
541 insert.push(after);
542 }
543 }
544 else if (after === null || after === void 0 ? void 0 : after.breakAfter) {
545 // The element at `toI` is entirely covered by this range.
546 // Preserve its line break, if any.
547 if (last)
548 last.breakAfter = 1;
549 else
550 breakAtStart = 1;
551 }
552 // Since we've handled the next element from the current elements
553 // now, make sure `toI` points after that.
554 toI++;
555 }
556 if (before) {
557 before.breakAfter = breakAtStart;
558 if (fromOff > 0) {
559 if (!breakAtStart && insert.length && before.merge(fromOff, before.length, insert[0], false, openStart, 0)) {
560 before.breakAfter = insert.shift().breakAfter;
561 }
562 else if (fromOff < before.length || before.children.length && before.children[before.children.length - 1].length == 0) {
563 before.merge(fromOff, before.length, null, false, openStart, 0);
564 }
565 fromI++;
566 }
567 }
568 // Try to merge widgets on the boundaries of the replacement
569 while (fromI < toI && insert.length) {
570 if (children[toI - 1].become(insert[insert.length - 1])) {
571 toI--;
572 insert.pop();
573 openEnd = insert.length ? 0 : openStart;
574 }
575 else if (children[fromI].become(insert[0])) {
576 fromI++;
577 insert.shift();
578 openStart = insert.length ? 0 : openEnd;
579 }
580 else {
581 break;
582 }
583 }
584 if (!insert.length && fromI && toI < children.length && !children[fromI - 1].breakAfter &&
585 children[toI].merge(0, 0, children[fromI - 1], false, openStart, openEnd))
586 fromI--;
587 if (fromI < toI || insert.length)
588 parent.replaceChildren(fromI, toI, insert);
589}
590function mergeChildrenInto(parent, from, to, insert, openStart, openEnd) {
591 let cur = parent.childCursor();
592 let { i: toI, off: toOff } = cur.findPos(to, 1);
593 let { i: fromI, off: fromOff } = cur.findPos(from, -1);
594 let dLen = from - to;
595 for (let view of insert)
596 dLen += view.length;
597 parent.length += dLen;
598 replaceRange(parent, fromI, fromOff, toI, toOff, insert, 0, openStart, openEnd);
599}
600
601let nav = typeof navigator != "undefined" ? navigator : { userAgent: "", vendor: "", platform: "" };
602let doc = typeof document != "undefined" ? document : { documentElement: { style: {} } };
603const ie_edge = /*@__PURE__*//Edge\/(\d+)/.exec(nav.userAgent);
604const ie_upto10 = /*@__PURE__*//MSIE \d/.test(nav.userAgent);
605const ie_11up = /*@__PURE__*//Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(nav.userAgent);
606const ie = !!(ie_upto10 || ie_11up || ie_edge);
607const gecko = !ie && /*@__PURE__*//gecko\/(\d+)/i.test(nav.userAgent);
608const chrome = !ie && /*@__PURE__*//Chrome\/(\d+)/.exec(nav.userAgent);
609const webkit = "webkitFontSmoothing" in doc.documentElement.style;
610const safari = !ie && /*@__PURE__*//Apple Computer/.test(nav.vendor);
611const ios = safari && (/*@__PURE__*//Mobile\/\w+/.test(nav.userAgent) || nav.maxTouchPoints > 2);
612var browser = {
613 mac: ios || /*@__PURE__*//Mac/.test(nav.platform),
614 windows: /*@__PURE__*//Win/.test(nav.platform),
615 linux: /*@__PURE__*//Linux|X11/.test(nav.platform),
616 ie,
617 ie_version: ie_upto10 ? doc.documentMode || 6 : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0,
618 gecko,
619 gecko_version: gecko ? +(/*@__PURE__*//Firefox\/(\d+)/.exec(nav.userAgent) || [0, 0])[1] : 0,
620 chrome: !!chrome,
621 chrome_version: chrome ? +chrome[1] : 0,
622 ios,
623 android: /*@__PURE__*//Android\b/.test(nav.userAgent),
624 webkit,
625 safari,
626 webkit_version: webkit ? +(/*@__PURE__*//\bAppleWebKit\/(\d+)/.exec(navigator.userAgent) || [0, 0])[1] : 0,
627 tabSize: doc.documentElement.style.tabSize != null ? "tab-size" : "-moz-tab-size"
628};
629
630const MaxJoinLen = 256;
631class TextView extends ContentView {
632 constructor(text) {
633 super();
634 this.text = text;
635 }
636 get length() { return this.text.length; }
637 createDOM(textDOM) {
638 this.setDOM(textDOM || document.createTextNode(this.text));
639 }
640 sync(track) {
641 if (!this.dom)
642 this.createDOM();
643 if (this.dom.nodeValue != this.text) {
644 if (track && track.node == this.dom)
645 track.written = true;
646 this.dom.nodeValue = this.text;
647 }
648 }
649 reuseDOM(dom) {
650 if (dom.nodeType == 3)
651 this.createDOM(dom);
652 }
653 merge(from, to, source) {
654 if (source && (!(source instanceof TextView) || this.length - (to - from) + source.length > MaxJoinLen))
655 return false;
656 this.text = this.text.slice(0, from) + (source ? source.text : "") + this.text.slice(to);
657 this.markDirty();
658 return true;
659 }
660 split(from) {
661 let result = new TextView(this.text.slice(from));
662 this.text = this.text.slice(0, from);
663 this.markDirty();
664 return result;
665 }
666 localPosFromDOM(node, offset) {
667 return node == this.dom ? offset : offset ? this.text.length : 0;
668 }
669 domAtPos(pos) { return new DOMPos(this.dom, pos); }
670 domBoundsAround(_from, _to, offset) {
671 return { from: offset, to: offset + this.length, startDOM: this.dom, endDOM: this.dom.nextSibling };
672 }
673 coordsAt(pos, side) {
674 return textCoords(this.dom, pos, side);
675 }
676}
677class MarkView extends ContentView {
678 constructor(mark, children = [], length = 0) {
679 super();
680 this.mark = mark;
681 this.children = children;
682 this.length = length;
683 for (let ch of children)
684 ch.setParent(this);
685 }
686 setAttrs(dom) {
687 clearAttributes(dom);
688 if (this.mark.class)
689 dom.className = this.mark.class;
690 if (this.mark.attrs)
691 for (let name in this.mark.attrs)
692 dom.setAttribute(name, this.mark.attrs[name]);
693 return dom;
694 }
695 reuseDOM(node) {
696 if (node.nodeName == this.mark.tagName.toUpperCase()) {
697 this.setDOM(node);
698 this.dirty |= 4 /* Attrs */ | 2 /* Node */;
699 }
700 }
701 sync(track) {
702 if (!this.dom)
703 this.setDOM(this.setAttrs(document.createElement(this.mark.tagName)));
704 else if (this.dirty & 4 /* Attrs */)
705 this.setAttrs(this.dom);
706 super.sync(track);
707 }
708 merge(from, to, source, _hasStart, openStart, openEnd) {
709 if (source && (!(source instanceof MarkView && source.mark.eq(this.mark)) ||
710 (from && openStart <= 0) || (to < this.length && openEnd <= 0)))
711 return false;
712 mergeChildrenInto(this, from, to, source ? source.children : [], openStart - 1, openEnd - 1);
713 this.markDirty();
714 return true;
715 }
716 split(from) {
717 let result = [], off = 0, detachFrom = -1, i = 0;
718 for (let elt of this.children) {
719 let end = off + elt.length;
720 if (end > from)
721 result.push(off < from ? elt.split(from - off) : elt);
722 if (detachFrom < 0 && off >= from)
723 detachFrom = i;
724 off = end;
725 i++;
726 }
727 let length = this.length - from;
728 this.length = from;
729 if (detachFrom > -1) {
730 this.children.length = detachFrom;
731 this.markDirty();
732 }
733 return new MarkView(this.mark, result, length);
734 }
735 domAtPos(pos) {
736 return inlineDOMAtPos(this.dom, this.children, pos);
737 }
738 coordsAt(pos, side) {
739 return coordsInChildren(this, pos, side);
740 }
741}
742function textCoords(text, pos, side) {
743 let length = text.nodeValue.length;
744 if (pos > length)
745 pos = length;
746 let from = pos, to = pos, flatten = 0;
747 if (pos == 0 && side < 0 || pos == length && side >= 0) {
748 if (!(browser.chrome || browser.gecko)) { // These browsers reliably return valid rectangles for empty ranges
749 if (pos) {
750 from--;
751 flatten = 1;
752 } // FIXME this is wrong in RTL text
753 else if (to < length) {
754 to++;
755 flatten = -1;
756 }
757 }
758 }
759 else {
760 if (side < 0)
761 from--;
762 else if (to < length)
763 to++;
764 }
765 let rects = textRange(text, from, to).getClientRects();
766 if (!rects.length)
767 return Rect0;
768 let rect = rects[(flatten ? flatten < 0 : side >= 0) ? 0 : rects.length - 1];
769 if (browser.safari && !flatten && rect.width == 0)
770 rect = Array.prototype.find.call(rects, r => r.width) || rect;
771 return flatten ? flattenRect(rect, flatten < 0) : rect || null;
772}
773// Also used for collapsed ranges that don't have a placeholder widget!
774class WidgetView extends ContentView {
775 constructor(widget, length, side) {
776 super();
777 this.widget = widget;
778 this.length = length;
779 this.side = side;
780 this.prevWidget = null;
781 }
782 static create(widget, length, side) {
783 return new (widget.customView || WidgetView)(widget, length, side);
784 }
785 split(from) {
786 let result = WidgetView.create(this.widget, this.length - from, this.side);
787 this.length -= from;
788 return result;
789 }
790 sync() {
791 if (!this.dom || !this.widget.updateDOM(this.dom)) {
792 if (this.dom && this.prevWidget)
793 this.prevWidget.destroy(this.dom);
794 this.prevWidget = null;
795 this.setDOM(this.widget.toDOM(this.editorView));
796 this.dom.contentEditable = "false";
797 }
798 }
799 getSide() { return this.side; }
800 merge(from, to, source, hasStart, openStart, openEnd) {
801 if (source && (!(source instanceof WidgetView) || !this.widget.compare(source.widget) ||
802 from > 0 && openStart <= 0 || to < this.length && openEnd <= 0))
803 return false;
804 this.length = from + (source ? source.length : 0) + (this.length - to);
805 return true;
806 }
807 become(other) {
808 if (other.length == this.length && other instanceof WidgetView && other.side == this.side) {
809 if (this.widget.constructor == other.widget.constructor) {
810 if (!this.widget.eq(other.widget))
811 this.markDirty(true);
812 if (this.dom && !this.prevWidget)
813 this.prevWidget = this.widget;
814 this.widget = other.widget;
815 return true;
816 }
817 }
818 return false;
819 }
820 ignoreMutation() { return true; }
821 ignoreEvent(event) { return this.widget.ignoreEvent(event); }
822 get overrideDOMText() {
823 if (this.length == 0)
824 return Text.empty;
825 let top = this;
826 while (top.parent)
827 top = top.parent;
828 let view = top.editorView, text = view && view.state.doc, start = this.posAtStart;
829 return text ? text.slice(start, start + this.length) : Text.empty;
830 }
831 domAtPos(pos) {
832 return pos == 0 ? DOMPos.before(this.dom) : DOMPos.after(this.dom, pos == this.length);
833 }
834 domBoundsAround() { return null; }
835 coordsAt(pos, side) {
836 let rects = this.dom.getClientRects(), rect = null;
837 if (!rects.length)
838 return Rect0;
839 for (let i = pos > 0 ? rects.length - 1 : 0;; i += (pos > 0 ? -1 : 1)) {
840 rect = rects[i];
841 if (pos > 0 ? i == 0 : i == rects.length - 1 || rect.top < rect.bottom)
842 break;
843 }
844 return (pos == 0 && side > 0 || pos == this.length && side <= 0) ? rect : flattenRect(rect, pos == 0);
845 }
846 get isEditable() { return false; }
847 destroy() {
848 super.destroy();
849 if (this.dom)
850 this.widget.destroy(this.dom);
851 }
852}
853class CompositionView extends WidgetView {
854 domAtPos(pos) {
855 let { topView, text } = this.widget;
856 if (!topView)
857 return new DOMPos(text, Math.min(pos, text.nodeValue.length));
858 return scanCompositionTree(pos, 0, topView, text, (v, p) => v.domAtPos(p), p => new DOMPos(text, Math.min(p, text.nodeValue.length)));
859 }
860 sync() { this.setDOM(this.widget.toDOM()); }
861 localPosFromDOM(node, offset) {
862 let { topView, text } = this.widget;
863 if (!topView)
864 return Math.min(offset, this.length);
865 return posFromDOMInCompositionTree(node, offset, topView, text);
866 }
867 ignoreMutation() { return false; }
868 get overrideDOMText() { return null; }
869 coordsAt(pos, side) {
870 let { topView, text } = this.widget;
871 if (!topView)
872 return textCoords(text, pos, side);
873 return scanCompositionTree(pos, side, topView, text, (v, pos, side) => v.coordsAt(pos, side), (pos, side) => textCoords(text, pos, side));
874 }
875 destroy() {
876 var _a;
877 super.destroy();
878 (_a = this.widget.topView) === null || _a === void 0 ? void 0 : _a.destroy();
879 }
880 get isEditable() { return true; }
881}
882// Uses the old structure of a chunk of content view frozen for
883// composition to try and find a reasonable DOM location for the given
884// offset.
885function scanCompositionTree(pos, side, view, text, enterView, fromText) {
886 if (view instanceof MarkView) {
887 for (let child of view.children) {
888 let hasComp = contains(child.dom, text);
889 let len = hasComp ? text.nodeValue.length : child.length;
890 if (pos < len || pos == len && child.getSide() <= 0)
891 return hasComp ? scanCompositionTree(pos, side, child, text, enterView, fromText) : enterView(child, pos, side);
892 pos -= len;
893 }
894 return enterView(view, view.length, -1);
895 }
896 else if (view.dom == text) {
897 return fromText(pos, side);
898 }
899 else {
900 return enterView(view, pos, side);
901 }
902}
903function posFromDOMInCompositionTree(node, offset, view, text) {
904 if (view instanceof MarkView) {
905 for (let child of view.children) {
906 let pos = 0, hasComp = contains(child.dom, text);
907 if (contains(child.dom, node))
908 return pos + (hasComp ? posFromDOMInCompositionTree(node, offset, child, text) : child.localPosFromDOM(node, offset));
909 pos += hasComp ? text.nodeValue.length : child.length;
910 }
911 }
912 else if (view.dom == text) {
913 return Math.min(offset, text.nodeValue.length);
914 }
915 return view.localPosFromDOM(node, offset);
916}
917// These are drawn around uneditable widgets to avoid a number of
918// browser bugs that show up when the cursor is directly next to
919// uneditable inline content.
920class WidgetBufferView extends ContentView {
921 constructor(side) {
922 super();
923 this.side = side;
924 }
925 get length() { return 0; }
926 merge() { return false; }
927 become(other) {
928 return other instanceof WidgetBufferView && other.side == this.side;
929 }
930 split() { return new WidgetBufferView(this.side); }
931 sync() {
932 if (!this.dom) {
933 let dom = document.createElement("img");
934 dom.className = "cm-widgetBuffer";
935 dom.setAttribute("aria-hidden", "true");
936 this.setDOM(dom);
937 }
938 }
939 getSide() { return this.side; }
940 domAtPos(pos) { return DOMPos.before(this.dom); }
941 localPosFromDOM() { return 0; }
942 domBoundsAround() { return null; }
943 coordsAt(pos) {
944 let imgRect = this.dom.getBoundingClientRect();
945 // Since the <img> height doesn't correspond to text height, try
946 // to borrow the height from some sibling node.
947 let siblingRect = inlineSiblingRect(this, this.side > 0 ? -1 : 1);
948 return siblingRect && siblingRect.top < imgRect.bottom && siblingRect.bottom > imgRect.top
949 ? { left: imgRect.left, right: imgRect.right, top: siblingRect.top, bottom: siblingRect.bottom } : imgRect;
950 }
951 get overrideDOMText() {
952 return Text.empty;
953 }
954}
955TextView.prototype.children = WidgetView.prototype.children = WidgetBufferView.prototype.children = noChildren;
956function inlineSiblingRect(view, side) {
957 let parent = view.parent, index = parent ? parent.children.indexOf(view) : -1;
958 while (parent && index >= 0) {
959 if (side < 0 ? index > 0 : index < parent.children.length) {
960 let next = parent.children[index + side];
961 if (next instanceof TextView) {
962 let nextRect = next.coordsAt(side < 0 ? next.length : 0, side);
963 if (nextRect)
964 return nextRect;
965 }
966 index += side;
967 }
968 else if (parent instanceof MarkView && parent.parent) {
969 index = parent.parent.children.indexOf(parent) + (side < 0 ? 0 : 1);
970 parent = parent.parent;
971 }
972 else {
973 let last = parent.dom.lastChild;
974 if (last && last.nodeName == "BR")
975 return last.getClientRects()[0];
976 break;
977 }
978 }
979 return undefined;
980}
981function inlineDOMAtPos(dom, children, pos) {
982 let i = 0;
983 for (let off = 0; i < children.length; i++) {
984 let child = children[i], end = off + child.length;
985 if (end == off && child.getSide() <= 0)
986 continue;
987 if (pos > off && pos < end && child.dom.parentNode == dom)
988 return child.domAtPos(pos - off);
989 if (pos <= off)
990 break;
991 off = end;
992 }
993 for (; i > 0; i--) {
994 let before = children[i - 1].dom;
995 if (before.parentNode == dom)
996 return DOMPos.after(before);
997 }
998 return new DOMPos(dom, 0);
999}
1000// Assumes `view`, if a mark view, has precisely 1 child.
1001function joinInlineInto(parent, view, open) {
1002 let last, { children } = parent;
1003 if (open > 0 && view instanceof MarkView && children.length &&
1004 (last = children[children.length - 1]) instanceof MarkView && last.mark.eq(view.mark)) {
1005 joinInlineInto(last, view.children[0], open - 1);
1006 }
1007 else {
1008 children.push(view);
1009 view.setParent(parent);
1010 }
1011 parent.length += view.length;
1012}
1013function coordsInChildren(view, pos, side) {
1014 for (let off = 0, i = 0; i < view.children.length; i++) {
1015 let child = view.children[i], end = off + child.length, next;
1016 if ((side <= 0 || end == view.length || child.getSide() > 0 ? end >= pos : end > pos) &&
1017 (pos < end || i + 1 == view.children.length || (next = view.children[i + 1]).length || next.getSide() > 0)) {
1018 let flatten = 0;
1019 if (end == off) {
1020 if (child.getSide() <= 0)
1021 continue;
1022 flatten = side = -child.getSide();
1023 }
1024 let rect = child.coordsAt(Math.max(0, pos - off), side);
1025 return flatten && rect ? flattenRect(rect, side < 0) : rect;
1026 }
1027 off = end;
1028 }
1029 let last = view.dom.lastChild;
1030 if (!last)
1031 return view.dom.getBoundingClientRect();
1032 let rects = clientRectsFor(last);
1033 return rects[rects.length - 1] || null;
1034}
1035
1036function combineAttrs(source, target) {
1037 for (let name in source) {
1038 if (name == "class" && target.class)
1039 target.class += " " + source.class;
1040 else if (name == "style" && target.style)
1041 target.style += ";" + source.style;
1042 else
1043 target[name] = source[name];
1044 }
1045 return target;
1046}
1047function attrsEq(a, b) {
1048 if (a == b)
1049 return true;
1050 if (!a || !b)
1051 return false;
1052 let keysA = Object.keys(a), keysB = Object.keys(b);
1053 if (keysA.length != keysB.length)
1054 return false;
1055 for (let key of keysA) {
1056 if (keysB.indexOf(key) == -1 || a[key] !== b[key])
1057 return false;
1058 }
1059 return true;
1060}
1061function updateAttrs(dom, prev, attrs) {
1062 let changed = null;
1063 if (prev)
1064 for (let name in prev)
1065 if (!(attrs && name in attrs))
1066 dom.removeAttribute(changed = name);
1067 if (attrs)
1068 for (let name in attrs)
1069 if (!(prev && prev[name] == attrs[name]))
1070 dom.setAttribute(changed = name, attrs[name]);
1071 return !!changed;
1072}
1073
1074/**
1075Widgets added to the content are described by subclasses of this
1076class. Using a description object like that makes it possible to
1077delay creating of the DOM structure for a widget until it is
1078needed, and to avoid redrawing widgets even if the decorations
1079that define them are recreated.
1080*/
1081class WidgetType {
1082 /**
1083 Compare this instance to another instance of the same type.
1084 (TypeScript can't express this, but only instances of the same
1085 specific class will be passed to this method.) This is used to
1086 avoid redrawing widgets when they are replaced by a new
1087 decoration of the same type. The default implementation just
1088 returns `false`, which will cause new instances of the widget to
1089 always be redrawn.
1090 */
1091 eq(widget) { return false; }
1092 /**
1093 Update a DOM element created by a widget of the same type (but
1094 different, non-`eq` content) to reflect this widget. May return
1095 true to indicate that it could update, false to indicate it
1096 couldn't (in which case the widget will be redrawn). The default
1097 implementation just returns false.
1098 */
1099 updateDOM(dom) { return false; }
1100 /**
1101 @internal
1102 */
1103 compare(other) {
1104 return this == other || this.constructor == other.constructor && this.eq(other);
1105 }
1106 /**
1107 The estimated height this widget will have, to be used when
1108 estimating the height of content that hasn't been drawn. May
1109 return -1 to indicate you don't know. The default implementation
1110 returns -1.
1111 */
1112 get estimatedHeight() { return -1; }
1113 /**
1114 Can be used to configure which kinds of events inside the widget
1115 should be ignored by the editor. The default is to ignore all
1116 events.
1117 */
1118 ignoreEvent(event) { return true; }
1119 /**
1120 @internal
1121 */
1122 get customView() { return null; }
1123 /**
1124 This is called when the an instance of the widget is removed
1125 from the editor view.
1126 */
1127 destroy(dom) { }
1128}
1129/**
1130The different types of blocks that can occur in an editor view.
1131*/
1132var BlockType = /*@__PURE__*/(function (BlockType) {
1133 /**
1134 A line of text.
1135 */
1136 BlockType[BlockType["Text"] = 0] = "Text";
1137 /**
1138 A block widget associated with the position after it.
1139 */
1140 BlockType[BlockType["WidgetBefore"] = 1] = "WidgetBefore";
1141 /**
1142 A block widget associated with the position before it.
1143 */
1144 BlockType[BlockType["WidgetAfter"] = 2] = "WidgetAfter";
1145 /**
1146 A block widget [replacing](https://codemirror.net/6/docs/ref/#view.Decoration^replace) a range of content.
1147 */
1148 BlockType[BlockType["WidgetRange"] = 3] = "WidgetRange";
1149return BlockType})(BlockType || (BlockType = {}));
1150/**
1151A decoration provides information on how to draw or style a piece
1152of content. You'll usually use it wrapped in a
1153[`Range`](https://codemirror.net/6/docs/ref/#state.Range), which adds a start and end position.
1154@nonabstract
1155*/
1156class Decoration extends RangeValue {
1157 constructor(
1158 /**
1159 @internal
1160 */
1161 startSide,
1162 /**
1163 @internal
1164 */
1165 endSide,
1166 /**
1167 @internal
1168 */
1169 widget,
1170 /**
1171 The config object used to create this decoration. You can
1172 include additional properties in there to store metadata about
1173 your decoration.
1174 */
1175 spec) {
1176 super();
1177 this.startSide = startSide;
1178 this.endSide = endSide;
1179 this.widget = widget;
1180 this.spec = spec;
1181 }
1182 /**
1183 @internal
1184 */
1185 get heightRelevant() { return false; }
1186 /**
1187 Create a mark decoration, which influences the styling of the
1188 content in its range. Nested mark decorations will cause nested
1189 DOM elements to be created. Nesting order is determined by
1190 precedence of the [facet](https://codemirror.net/6/docs/ref/#view.EditorView^decorations), with
1191 the higher-precedence decorations creating the inner DOM nodes.
1192 Such elements are split on line boundaries and on the boundaries
1193 of lower-precedence decorations.
1194 */
1195 static mark(spec) {
1196 return new MarkDecoration(spec);
1197 }
1198 /**
1199 Create a widget decoration, which displays a DOM element at the
1200 given position.
1201 */
1202 static widget(spec) {
1203 let side = spec.side || 0, block = !!spec.block;
1204 side += block ? (side > 0 ? 300000000 /* BlockAfter */ : -400000000 /* BlockBefore */) : (side > 0 ? 100000000 /* InlineAfter */ : -100000000 /* InlineBefore */);
1205 return new PointDecoration(spec, side, side, block, spec.widget || null, false);
1206 }
1207 /**
1208 Create a replace decoration which replaces the given range with
1209 a widget, or simply hides it.
1210 */
1211 static replace(spec) {
1212 let block = !!spec.block, startSide, endSide;
1213 if (spec.isBlockGap) {
1214 startSide = -500000000 /* GapStart */;
1215 endSide = 400000000 /* GapEnd */;
1216 }
1217 else {
1218 let { start, end } = getInclusive(spec, block);
1219 startSide = (start ? (block ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 500000000 /* NonIncStart */) - 1;
1220 endSide = (end ? (block ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -600000000 /* NonIncEnd */) + 1;
1221 }
1222 return new PointDecoration(spec, startSide, endSide, block, spec.widget || null, true);
1223 }
1224 /**
1225 Create a line decoration, which can add DOM attributes to the
1226 line starting at the given position.
1227 */
1228 static line(spec) {
1229 return new LineDecoration(spec);
1230 }
1231 /**
1232 Build a [`DecorationSet`](https://codemirror.net/6/docs/ref/#view.DecorationSet) from the given
1233 decorated range or ranges. If the ranges aren't already sorted,
1234 pass `true` for `sort` to make the library sort them for you.
1235 */
1236 static set(of, sort = false) {
1237 return RangeSet.of(of, sort);
1238 }
1239 /**
1240 @internal
1241 */
1242 hasHeight() { return this.widget ? this.widget.estimatedHeight > -1 : false; }
1243}
1244/**
1245The empty set of decorations.
1246*/
1247Decoration.none = RangeSet.empty;
1248class MarkDecoration extends Decoration {
1249 constructor(spec) {
1250 let { start, end } = getInclusive(spec);
1251 super(start ? -1 /* InlineIncStart */ : 500000000 /* NonIncStart */, end ? 1 /* InlineIncEnd */ : -600000000 /* NonIncEnd */, null, spec);
1252 this.tagName = spec.tagName || "span";
1253 this.class = spec.class || "";
1254 this.attrs = spec.attributes || null;
1255 }
1256 eq(other) {
1257 return this == other ||
1258 other instanceof MarkDecoration &&
1259 this.tagName == other.tagName &&
1260 this.class == other.class &&
1261 attrsEq(this.attrs, other.attrs);
1262 }
1263 range(from, to = from) {
1264 if (from >= to)
1265 throw new RangeError("Mark decorations may not be empty");
1266 return super.range(from, to);
1267 }
1268}
1269MarkDecoration.prototype.point = false;
1270class LineDecoration extends Decoration {
1271 constructor(spec) {
1272 super(-200000000 /* Line */, -200000000 /* Line */, null, spec);
1273 }
1274 eq(other) {
1275 return other instanceof LineDecoration && attrsEq(this.spec.attributes, other.spec.attributes);
1276 }
1277 range(from, to = from) {
1278 if (to != from)
1279 throw new RangeError("Line decoration ranges must be zero-length");
1280 return super.range(from, to);
1281 }
1282}
1283LineDecoration.prototype.mapMode = MapMode.TrackBefore;
1284LineDecoration.prototype.point = true;
1285class PointDecoration extends Decoration {
1286 constructor(spec, startSide, endSide, block, widget, isReplace) {
1287 super(startSide, endSide, widget, spec);
1288 this.block = block;
1289 this.isReplace = isReplace;
1290 this.mapMode = !block ? MapMode.TrackDel : startSide <= 0 ? MapMode.TrackBefore : MapMode.TrackAfter;
1291 }
1292 // Only relevant when this.block == true
1293 get type() {
1294 return this.startSide < this.endSide ? BlockType.WidgetRange
1295 : this.startSide <= 0 ? BlockType.WidgetBefore : BlockType.WidgetAfter;
1296 }
1297 get heightRelevant() { return this.block || !!this.widget && this.widget.estimatedHeight >= 5; }
1298 eq(other) {
1299 return other instanceof PointDecoration &&
1300 widgetsEq(this.widget, other.widget) &&
1301 this.block == other.block &&
1302 this.startSide == other.startSide && this.endSide == other.endSide;
1303 }
1304 range(from, to = from) {
1305 if (this.isReplace && (from > to || (from == to && this.startSide > 0 && this.endSide <= 0)))
1306 throw new RangeError("Invalid range for replacement decoration");
1307 if (!this.isReplace && to != from)
1308 throw new RangeError("Widget decorations can only have zero-length ranges");
1309 return super.range(from, to);
1310 }
1311}
1312PointDecoration.prototype.point = true;
1313function getInclusive(spec, block = false) {
1314 let { inclusiveStart: start, inclusiveEnd: end } = spec;
1315 if (start == null)
1316 start = spec.inclusive;
1317 if (end == null)
1318 end = spec.inclusive;
1319 return { start: start !== null && start !== void 0 ? start : block, end: end !== null && end !== void 0 ? end : block };
1320}
1321function widgetsEq(a, b) {
1322 return a == b || !!(a && b && a.compare(b));
1323}
1324function addRange(from, to, ranges, margin = 0) {
1325 let last = ranges.length - 1;
1326 if (last >= 0 && ranges[last] + margin >= from)
1327 ranges[last] = Math.max(ranges[last], to);
1328 else
1329 ranges.push(from, to);
1330}
1331
1332class LineView extends ContentView {
1333 constructor() {
1334 super(...arguments);
1335 this.children = [];
1336 this.length = 0;
1337 this.prevAttrs = undefined;
1338 this.attrs = null;
1339 this.breakAfter = 0;
1340 }
1341 // Consumes source
1342 merge(from, to, source, hasStart, openStart, openEnd) {
1343 if (source) {
1344 if (!(source instanceof LineView))
1345 return false;
1346 if (!this.dom)
1347 source.transferDOM(this); // Reuse source.dom when appropriate
1348 }
1349 if (hasStart)
1350 this.setDeco(source ? source.attrs : null);
1351 mergeChildrenInto(this, from, to, source ? source.children : [], openStart, openEnd);
1352 return true;
1353 }
1354 split(at) {
1355 let end = new LineView;
1356 end.breakAfter = this.breakAfter;
1357 if (this.length == 0)
1358 return end;
1359 let { i, off } = this.childPos(at);
1360 if (off) {
1361 end.append(this.children[i].split(off), 0);
1362 this.children[i].merge(off, this.children[i].length, null, false, 0, 0);
1363 i++;
1364 }
1365 for (let j = i; j < this.children.length; j++)
1366 end.append(this.children[j], 0);
1367 while (i > 0 && this.children[i - 1].length == 0)
1368 this.children[--i].destroy();
1369 this.children.length = i;
1370 this.markDirty();
1371 this.length = at;
1372 return end;
1373 }
1374 transferDOM(other) {
1375 if (!this.dom)
1376 return;
1377 this.markDirty();
1378 other.setDOM(this.dom);
1379 other.prevAttrs = this.prevAttrs === undefined ? this.attrs : this.prevAttrs;
1380 this.prevAttrs = undefined;
1381 this.dom = null;
1382 }
1383 setDeco(attrs) {
1384 if (!attrsEq(this.attrs, attrs)) {
1385 if (this.dom) {
1386 this.prevAttrs = this.attrs;
1387 this.markDirty();
1388 }
1389 this.attrs = attrs;
1390 }
1391 }
1392 append(child, openStart) {
1393 joinInlineInto(this, child, openStart);
1394 }
1395 // Only called when building a line view in ContentBuilder
1396 addLineDeco(deco) {
1397 let attrs = deco.spec.attributes, cls = deco.spec.class;
1398 if (attrs)
1399 this.attrs = combineAttrs(attrs, this.attrs || {});
1400 if (cls)
1401 this.attrs = combineAttrs({ class: cls }, this.attrs || {});
1402 }
1403 domAtPos(pos) {
1404 return inlineDOMAtPos(this.dom, this.children, pos);
1405 }
1406 reuseDOM(node) {
1407 if (node.nodeName == "DIV") {
1408 this.setDOM(node);
1409 this.dirty |= 4 /* Attrs */ | 2 /* Node */;
1410 }
1411 }
1412 sync(track) {
1413 var _a;
1414 if (!this.dom) {
1415 this.setDOM(document.createElement("div"));
1416 this.dom.className = "cm-line";
1417 this.prevAttrs = this.attrs ? null : undefined;
1418 }
1419 else if (this.dirty & 4 /* Attrs */) {
1420 clearAttributes(this.dom);
1421 this.dom.className = "cm-line";
1422 this.prevAttrs = this.attrs ? null : undefined;
1423 }
1424 if (this.prevAttrs !== undefined) {
1425 updateAttrs(this.dom, this.prevAttrs, this.attrs);
1426 this.dom.classList.add("cm-line");
1427 this.prevAttrs = undefined;
1428 }
1429 super.sync(track);
1430 let last = this.dom.lastChild;
1431 while (last && ContentView.get(last) instanceof MarkView)
1432 last = last.lastChild;
1433 if (!last || !this.length ||
1434 last.nodeName != "BR" && ((_a = ContentView.get(last)) === null || _a === void 0 ? void 0 : _a.isEditable) == false &&
1435 (!browser.ios || !this.children.some(ch => ch instanceof TextView))) {
1436 let hack = document.createElement("BR");
1437 hack.cmIgnore = true;
1438 this.dom.appendChild(hack);
1439 }
1440 }
1441 measureTextSize() {
1442 if (this.children.length == 0 || this.length > 20)
1443 return null;
1444 let totalWidth = 0;
1445 for (let child of this.children) {
1446 if (!(child instanceof TextView))
1447 return null;
1448 let rects = clientRectsFor(child.dom);
1449 if (rects.length != 1)
1450 return null;
1451 totalWidth += rects[0].width;
1452 }
1453 return { lineHeight: this.dom.getBoundingClientRect().height,
1454 charWidth: totalWidth / this.length };
1455 }
1456 coordsAt(pos, side) {
1457 return coordsInChildren(this, pos, side);
1458 }
1459 become(_other) { return false; }
1460 get type() { return BlockType.Text; }
1461 static find(docView, pos) {
1462 for (let i = 0, off = 0; i < docView.children.length; i++) {
1463 let block = docView.children[i], end = off + block.length;
1464 if (end >= pos) {
1465 if (block instanceof LineView)
1466 return block;
1467 if (end > pos)
1468 break;
1469 }
1470 off = end + block.breakAfter;
1471 }
1472 return null;
1473 }
1474}
1475class BlockWidgetView extends ContentView {
1476 constructor(widget, length, type) {
1477 super();
1478 this.widget = widget;
1479 this.length = length;
1480 this.type = type;
1481 this.breakAfter = 0;
1482 this.prevWidget = null;
1483 }
1484 merge(from, to, source, _takeDeco, openStart, openEnd) {
1485 if (source && (!(source instanceof BlockWidgetView) || !this.widget.compare(source.widget) ||
1486 from > 0 && openStart <= 0 || to < this.length && openEnd <= 0))
1487 return false;
1488 this.length = from + (source ? source.length : 0) + (this.length - to);
1489 return true;
1490 }
1491 domAtPos(pos) {
1492 return pos == 0 ? DOMPos.before(this.dom) : DOMPos.after(this.dom, pos == this.length);
1493 }
1494 split(at) {
1495 let len = this.length - at;
1496 this.length = at;
1497 let end = new BlockWidgetView(this.widget, len, this.type);
1498 end.breakAfter = this.breakAfter;
1499 return end;
1500 }
1501 get children() { return noChildren; }
1502 sync() {
1503 if (!this.dom || !this.widget.updateDOM(this.dom)) {
1504 if (this.dom && this.prevWidget)
1505 this.prevWidget.destroy(this.dom);
1506 this.prevWidget = null;
1507 this.setDOM(this.widget.toDOM(this.editorView));
1508 this.dom.contentEditable = "false";
1509 }
1510 }
1511 get overrideDOMText() {
1512 return this.parent ? this.parent.view.state.doc.slice(this.posAtStart, this.posAtEnd) : Text.empty;
1513 }
1514 domBoundsAround() { return null; }
1515 become(other) {
1516 if (other instanceof BlockWidgetView && other.type == this.type &&
1517 other.widget.constructor == this.widget.constructor) {
1518 if (!other.widget.eq(this.widget))
1519 this.markDirty(true);
1520 if (this.dom && !this.prevWidget)
1521 this.prevWidget = this.widget;
1522 this.widget = other.widget;
1523 this.length = other.length;
1524 this.breakAfter = other.breakAfter;
1525 return true;
1526 }
1527 return false;
1528 }
1529 ignoreMutation() { return true; }
1530 ignoreEvent(event) { return this.widget.ignoreEvent(event); }
1531 destroy() {
1532 super.destroy();
1533 if (this.dom)
1534 this.widget.destroy(this.dom);
1535 }
1536}
1537
1538class ContentBuilder {
1539 constructor(doc, pos, end, disallowBlockEffectsFor) {
1540 this.doc = doc;
1541 this.pos = pos;
1542 this.end = end;
1543 this.disallowBlockEffectsFor = disallowBlockEffectsFor;
1544 this.content = [];
1545 this.curLine = null;
1546 this.breakAtStart = 0;
1547 this.pendingBuffer = 0 /* No */;
1548 // Set to false directly after a widget that covers the position after it
1549 this.atCursorPos = true;
1550 this.openStart = -1;
1551 this.openEnd = -1;
1552 this.text = "";
1553 this.textOff = 0;
1554 this.cursor = doc.iter();
1555 this.skip = pos;
1556 }
1557 posCovered() {
1558 if (this.content.length == 0)
1559 return !this.breakAtStart && this.doc.lineAt(this.pos).from != this.pos;
1560 let last = this.content[this.content.length - 1];
1561 return !last.breakAfter && !(last instanceof BlockWidgetView && last.type == BlockType.WidgetBefore);
1562 }
1563 getLine() {
1564 if (!this.curLine) {
1565 this.content.push(this.curLine = new LineView);
1566 this.atCursorPos = true;
1567 }
1568 return this.curLine;
1569 }
1570 flushBuffer(active) {
1571 if (this.pendingBuffer) {
1572 this.curLine.append(wrapMarks(new WidgetBufferView(-1), active), active.length);
1573 this.pendingBuffer = 0 /* No */;
1574 }
1575 }
1576 addBlockWidget(view) {
1577 this.flushBuffer([]);
1578 this.curLine = null;
1579 this.content.push(view);
1580 }
1581 finish(openEnd) {
1582 if (!openEnd)
1583 this.flushBuffer([]);
1584 else
1585 this.pendingBuffer = 0 /* No */;
1586 if (!this.posCovered())
1587 this.getLine();
1588 }
1589 buildText(length, active, openStart) {
1590 while (length > 0) {
1591 if (this.textOff == this.text.length) {
1592 let { value, lineBreak, done } = this.cursor.next(this.skip);
1593 this.skip = 0;
1594 if (done)
1595 throw new Error("Ran out of text content when drawing inline views");
1596 if (lineBreak) {
1597 if (!this.posCovered())
1598 this.getLine();
1599 if (this.content.length)
1600 this.content[this.content.length - 1].breakAfter = 1;
1601 else
1602 this.breakAtStart = 1;
1603 this.flushBuffer([]);
1604 this.curLine = null;
1605 length--;
1606 continue;
1607 }
1608 else {
1609 this.text = value;
1610 this.textOff = 0;
1611 }
1612 }
1613 let take = Math.min(this.text.length - this.textOff, length, 512 /* Chunk */);
1614 this.flushBuffer(active.slice(0, openStart));
1615 this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
1616 this.atCursorPos = true;
1617 this.textOff += take;
1618 length -= take;
1619 openStart = 0;
1620 }
1621 }
1622 span(from, to, active, openStart) {
1623 this.buildText(to - from, active, openStart);
1624 this.pos = to;
1625 if (this.openStart < 0)
1626 this.openStart = openStart;
1627 }
1628 point(from, to, deco, active, openStart, index) {
1629 if (this.disallowBlockEffectsFor[index] && deco instanceof PointDecoration) {
1630 if (deco.block)
1631 throw new RangeError("Block decorations may not be specified via plugins");
1632 if (to > this.doc.lineAt(this.pos).to)
1633 throw new RangeError("Decorations that replace line breaks may not be specified via plugins");
1634 }
1635 let len = to - from;
1636 if (deco instanceof PointDecoration) {
1637 if (deco.block) {
1638 let { type } = deco;
1639 if (type == BlockType.WidgetAfter && !this.posCovered())
1640 this.getLine();
1641 this.addBlockWidget(new BlockWidgetView(deco.widget || new NullWidget("div"), len, type));
1642 }
1643 else {
1644 let view = WidgetView.create(deco.widget || new NullWidget("span"), len, deco.startSide);
1645 let cursorBefore = this.atCursorPos && !view.isEditable && openStart <= active.length && (from < to || deco.startSide > 0);
1646 let cursorAfter = !view.isEditable && (from < to || deco.startSide <= 0);
1647 let line = this.getLine();
1648 if (this.pendingBuffer == 2 /* IfCursor */ && !cursorBefore)
1649 this.pendingBuffer = 0 /* No */;
1650 this.flushBuffer(active);
1651 if (cursorBefore) {
1652 line.append(wrapMarks(new WidgetBufferView(1), active), openStart);
1653 openStart = active.length + Math.max(0, openStart - active.length);
1654 }
1655 line.append(wrapMarks(view, active), openStart);
1656 this.atCursorPos = cursorAfter;
1657 this.pendingBuffer = !cursorAfter ? 0 /* No */ : from < to ? 1 /* Yes */ : 2 /* IfCursor */;
1658 }
1659 }
1660 else if (this.doc.lineAt(this.pos).from == this.pos) { // Line decoration
1661 this.getLine().addLineDeco(deco);
1662 }
1663 if (len) {
1664 // Advance the iterator past the replaced content
1665 if (this.textOff + len <= this.text.length) {
1666 this.textOff += len;
1667 }
1668 else {
1669 this.skip += len - (this.text.length - this.textOff);
1670 this.text = "";
1671 this.textOff = 0;
1672 }
1673 this.pos = to;
1674 }
1675 if (this.openStart < 0)
1676 this.openStart = openStart;
1677 }
1678 static build(text, from, to, decorations, dynamicDecorationMap) {
1679 let builder = new ContentBuilder(text, from, to, dynamicDecorationMap);
1680 builder.openEnd = RangeSet.spans(decorations, from, to, builder);
1681 if (builder.openStart < 0)
1682 builder.openStart = builder.openEnd;
1683 builder.finish(builder.openEnd);
1684 return builder;
1685 }
1686}
1687function wrapMarks(view, active) {
1688 for (let mark of active)
1689 view = new MarkView(mark, [view], view.length);
1690 return view;
1691}
1692class NullWidget extends WidgetType {
1693 constructor(tag) {
1694 super();
1695 this.tag = tag;
1696 }
1697 eq(other) { return other.tag == this.tag; }
1698 toDOM() { return document.createElement(this.tag); }
1699 updateDOM(elt) { return elt.nodeName.toLowerCase() == this.tag; }
1700}
1701
1702const clickAddsSelectionRange = /*@__PURE__*/Facet.define();
1703const dragMovesSelection$1 = /*@__PURE__*/Facet.define();
1704const mouseSelectionStyle = /*@__PURE__*/Facet.define();
1705const exceptionSink = /*@__PURE__*/Facet.define();
1706const updateListener = /*@__PURE__*/Facet.define();
1707const inputHandler = /*@__PURE__*/Facet.define();
1708const perLineTextDirection = /*@__PURE__*/Facet.define({
1709 combine: values => values.some(x => x)
1710});
1711class ScrollTarget {
1712 constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5) {
1713 this.range = range;
1714 this.y = y;
1715 this.x = x;
1716 this.yMargin = yMargin;
1717 this.xMargin = xMargin;
1718 }
1719 map(changes) {
1720 return changes.empty ? this : new ScrollTarget(this.range.map(changes), this.y, this.x, this.yMargin, this.xMargin);
1721 }
1722}
1723const scrollIntoView = /*@__PURE__*/StateEffect.define({ map: (t, ch) => t.map(ch) });
1724/**
1725Log or report an unhandled exception in client code. Should
1726probably only be used by extension code that allows client code to
1727provide functions, and calls those functions in a context where an
1728exception can't be propagated to calling code in a reasonable way
1729(for example when in an event handler).
1730
1731Either calls a handler registered with
1732[`EditorView.exceptionSink`](https://codemirror.net/6/docs/ref/#view.EditorView^exceptionSink),
1733`window.onerror`, if defined, or `console.error` (in which case
1734it'll pass `context`, when given, as first argument).
1735*/
1736function logException(state, exception, context) {
1737 let handler = state.facet(exceptionSink);
1738 if (handler.length)
1739 handler[0](exception);
1740 else if (window.onerror)
1741 window.onerror(String(exception), context, undefined, undefined, exception);
1742 else if (context)
1743 console.error(context + ":", exception);
1744 else
1745 console.error(exception);
1746}
1747const editable = /*@__PURE__*/Facet.define({ combine: values => values.length ? values[0] : true });
1748let nextPluginID = 0;
1749const viewPlugin = /*@__PURE__*/Facet.define();
1750/**
1751View plugins associate stateful values with a view. They can
1752influence the way the content is drawn, and are notified of things
1753that happen in the view.
1754*/
1755class ViewPlugin {
1756 constructor(
1757 /**
1758 @internal
1759 */
1760 id,
1761 /**
1762 @internal
1763 */
1764 create,
1765 /**
1766 @internal
1767 */
1768 domEventHandlers, buildExtensions) {
1769 this.id = id;
1770 this.create = create;
1771 this.domEventHandlers = domEventHandlers;
1772 this.extension = buildExtensions(this);
1773 }
1774 /**
1775 Define a plugin from a constructor function that creates the
1776 plugin's value, given an editor view.
1777 */
1778 static define(create, spec) {
1779 const { eventHandlers, provide, decorations: deco } = spec || {};
1780 return new ViewPlugin(nextPluginID++, create, eventHandlers, plugin => {
1781 let ext = [viewPlugin.of(plugin)];
1782 if (deco)
1783 ext.push(decorations.of(view => {
1784 let pluginInst = view.plugin(plugin);
1785 return pluginInst ? deco(pluginInst) : Decoration.none;
1786 }));
1787 if (provide)
1788 ext.push(provide(plugin));
1789 return ext;
1790 });
1791 }
1792 /**
1793 Create a plugin for a class whose constructor takes a single
1794 editor view as argument.
1795 */
1796 static fromClass(cls, spec) {
1797 return ViewPlugin.define(view => new cls(view), spec);
1798 }
1799}
1800class PluginInstance {
1801 constructor(spec) {
1802 this.spec = spec;
1803 // When starting an update, all plugins have this field set to the
1804 // update object, indicating they need to be updated. When finished
1805 // updating, it is set to `false`. Retrieving a plugin that needs to
1806 // be updated with `view.plugin` forces an eager update.
1807 this.mustUpdate = null;
1808 // This is null when the plugin is initially created, but
1809 // initialized on the first update.
1810 this.value = null;
1811 }
1812 update(view) {
1813 if (!this.value) {
1814 if (this.spec) {
1815 try {
1816 this.value = this.spec.create(view);
1817 }
1818 catch (e) {
1819 logException(view.state, e, "CodeMirror plugin crashed");
1820 this.deactivate();
1821 }
1822 }
1823 }
1824 else if (this.mustUpdate) {
1825 let update = this.mustUpdate;
1826 this.mustUpdate = null;
1827 if (this.value.update) {
1828 try {
1829 this.value.update(update);
1830 }
1831 catch (e) {
1832 logException(update.state, e, "CodeMirror plugin crashed");
1833 if (this.value.destroy)
1834 try {
1835 this.value.destroy();
1836 }
1837 catch (_) { }
1838 this.deactivate();
1839 }
1840 }
1841 }
1842 return this;
1843 }
1844 destroy(view) {
1845 var _a;
1846 if ((_a = this.value) === null || _a === void 0 ? void 0 : _a.destroy) {
1847 try {
1848 this.value.destroy();
1849 }
1850 catch (e) {
1851 logException(view.state, e, "CodeMirror plugin crashed");
1852 }
1853 }
1854 }
1855 deactivate() {
1856 this.spec = this.value = null;
1857 }
1858}
1859const editorAttributes = /*@__PURE__*/Facet.define();
1860const contentAttributes = /*@__PURE__*/Facet.define();
1861// Provide decorations
1862const decorations = /*@__PURE__*/Facet.define();
1863const atomicRanges = /*@__PURE__*/Facet.define();
1864const scrollMargins = /*@__PURE__*/Facet.define();
1865const styleModule = /*@__PURE__*/Facet.define();
1866class ChangedRange {
1867 constructor(fromA, toA, fromB, toB) {
1868 this.fromA = fromA;
1869 this.toA = toA;
1870 this.fromB = fromB;
1871 this.toB = toB;
1872 }
1873 join(other) {
1874 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));
1875 }
1876 addToSet(set) {
1877 let i = set.length, me = this;
1878 for (; i > 0; i--) {
1879 let range = set[i - 1];
1880 if (range.fromA > me.toA)
1881 continue;
1882 if (range.toA < me.fromA)
1883 break;
1884 me = me.join(range);
1885 set.splice(i - 1, 1);
1886 }
1887 set.splice(i, 0, me);
1888 return set;
1889 }
1890 static extendWithRanges(diff, ranges) {
1891 if (ranges.length == 0)
1892 return diff;
1893 let result = [];
1894 for (let dI = 0, rI = 0, posA = 0, posB = 0;; dI++) {
1895 let next = dI == diff.length ? null : diff[dI], off = posA - posB;
1896 let end = next ? next.fromB : 1e9;
1897 while (rI < ranges.length && ranges[rI] < end) {
1898 let from = ranges[rI], to = ranges[rI + 1];
1899 let fromB = Math.max(posB, from), toB = Math.min(end, to);
1900 if (fromB <= toB)
1901 new ChangedRange(fromB + off, toB + off, fromB, toB).addToSet(result);
1902 if (to > end)
1903 break;
1904 else
1905 rI += 2;
1906 }
1907 if (!next)
1908 return result;
1909 new ChangedRange(next.fromA, next.toA, next.fromB, next.toB).addToSet(result);
1910 posA = next.toA;
1911 posB = next.toB;
1912 }
1913 }
1914}
1915/**
1916View [plugins](https://codemirror.net/6/docs/ref/#view.ViewPlugin) are given instances of this
1917class, which describe what happened, whenever the view is updated.
1918*/
1919class ViewUpdate {
1920 constructor(
1921 /**
1922 The editor view that the update is associated with.
1923 */
1924 view,
1925 /**
1926 The new editor state.
1927 */
1928 state,
1929 /**
1930 The transactions involved in the update. May be empty.
1931 */
1932 transactions) {
1933 this.view = view;
1934 this.state = state;
1935 this.transactions = transactions;
1936 /**
1937 @internal
1938 */
1939 this.flags = 0;
1940 this.startState = view.state;
1941 this.changes = ChangeSet.empty(this.startState.doc.length);
1942 for (let tr of transactions)
1943 this.changes = this.changes.compose(tr.changes);
1944 let changedRanges = [];
1945 this.changes.iterChangedRanges((fromA, toA, fromB, toB) => changedRanges.push(new ChangedRange(fromA, toA, fromB, toB)));
1946 this.changedRanges = changedRanges;
1947 let focus = view.hasFocus;
1948 if (focus != view.inputState.notifiedFocused) {
1949 view.inputState.notifiedFocused = focus;
1950 this.flags |= 1 /* Focus */;
1951 }
1952 }
1953 /**
1954 @internal
1955 */
1956 static create(view, state, transactions) {
1957 return new ViewUpdate(view, state, transactions);
1958 }
1959 /**
1960 Tells you whether the [viewport](https://codemirror.net/6/docs/ref/#view.EditorView.viewport) or
1961 [visible ranges](https://codemirror.net/6/docs/ref/#view.EditorView.visibleRanges) changed in this
1962 update.
1963 */
1964 get viewportChanged() {
1965 return (this.flags & 4 /* Viewport */) > 0;
1966 }
1967 /**
1968 Indicates whether the height of a block element in the editor
1969 changed in this update.
1970 */
1971 get heightChanged() {
1972 return (this.flags & 2 /* Height */) > 0;
1973 }
1974 /**
1975 Returns true when the document was modified or the size of the
1976 editor, or elements within the editor, changed.
1977 */
1978 get geometryChanged() {
1979 return this.docChanged || (this.flags & (8 /* Geometry */ | 2 /* Height */)) > 0;
1980 }
1981 /**
1982 True when this update indicates a focus change.
1983 */
1984 get focusChanged() {
1985 return (this.flags & 1 /* Focus */) > 0;
1986 }
1987 /**
1988 Whether the document changed in this update.
1989 */
1990 get docChanged() {
1991 return !this.changes.empty;
1992 }
1993 /**
1994 Whether the selection was explicitly set in this update.
1995 */
1996 get selectionSet() {
1997 return this.transactions.some(tr => tr.selection);
1998 }
1999 /**
2000 @internal
2001 */
2002 get empty() { return this.flags == 0 && this.transactions.length == 0; }
2003}
2004
2005/**
2006Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
2007*/
2008var Direction = /*@__PURE__*/(function (Direction) {
2009 // (These are chosen to match the base levels, in bidi algorithm
2010 // terms, of spans in that direction.)
2011 /**
2012 Left-to-right.
2013 */
2014 Direction[Direction["LTR"] = 0] = "LTR";
2015 /**
2016 Right-to-left.
2017 */
2018 Direction[Direction["RTL"] = 1] = "RTL";
2019return Direction})(Direction || (Direction = {}));
2020const LTR = Direction.LTR, RTL = Direction.RTL;
2021// Decode a string with each type encoded as log2(type)
2022function dec(str) {
2023 let result = [];
2024 for (let i = 0; i < str.length; i++)
2025 result.push(1 << +str[i]);
2026 return result;
2027}
2028// Character types for codepoints 0 to 0xf8
2029const LowTypes = /*@__PURE__*/dec("88888888888888888888888888888888888666888888787833333333337888888000000000000000000000000008888880000000000000000000000000088888888888888888888888888888888888887866668888088888663380888308888800000000000000000000000800000000000000000000000000000008");
2030// Character types for codepoints 0x600 to 0x6f9
2031const ArabicTypes = /*@__PURE__*/dec("4444448826627288999999999992222222222222222222222222222222222222222222222229999999999999999999994444444444644222822222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222999999949999999229989999223333333333");
2032const Brackets = /*@__PURE__*/Object.create(null), BracketStack = [];
2033// There's a lot more in
2034// https://www.unicode.org/Public/UCD/latest/ucd/BidiBrackets.txt,
2035// which are left out to keep code size down.
2036for (let p of ["()", "[]", "{}"]) {
2037 let l = /*@__PURE__*/p.charCodeAt(0), r = /*@__PURE__*/p.charCodeAt(1);
2038 Brackets[l] = r;
2039 Brackets[r] = -l;
2040}
2041function charType(ch) {
2042 return ch <= 0xf7 ? LowTypes[ch] :
2043 0x590 <= ch && ch <= 0x5f4 ? 2 /* R */ :
2044 0x600 <= ch && ch <= 0x6f9 ? ArabicTypes[ch - 0x600] :
2045 0x6ee <= ch && ch <= 0x8ac ? 4 /* AL */ :
2046 0x2000 <= ch && ch <= 0x200b ? 256 /* NI */ :
2047 ch == 0x200c ? 256 /* NI */ : 1 /* L */;
2048}
2049const BidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
2050/**
2051Represents a contiguous range of text that has a single direction
2052(as in left-to-right or right-to-left).
2053*/
2054class BidiSpan {
2055 /**
2056 @internal
2057 */
2058 constructor(
2059 /**
2060 The start of the span (relative to the start of the line).
2061 */
2062 from,
2063 /**
2064 The end of the span.
2065 */
2066 to,
2067 /**
2068 The ["bidi
2069 level"](https://unicode.org/reports/tr9/#Basic_Display_Algorithm)
2070 of the span (in this context, 0 means
2071 left-to-right, 1 means right-to-left, 2 means left-to-right
2072 number inside right-to-left text).
2073 */
2074 level) {
2075 this.from = from;
2076 this.to = to;
2077 this.level = level;
2078 }
2079 /**
2080 The direction of this span.
2081 */
2082 get dir() { return this.level % 2 ? RTL : LTR; }
2083 /**
2084 @internal
2085 */
2086 side(end, dir) { return (this.dir == dir) == end ? this.to : this.from; }
2087 /**
2088 @internal
2089 */
2090 static find(order, index, level, assoc) {
2091 let maybe = -1;
2092 for (let i = 0; i < order.length; i++) {
2093 let span = order[i];
2094 if (span.from <= index && span.to >= index) {
2095 if (span.level == level)
2096 return i;
2097 // When multiple spans match, if assoc != 0, take the one that
2098 // covers that side, otherwise take the one with the minimum
2099 // level.
2100 if (maybe < 0 || (assoc != 0 ? (assoc < 0 ? span.from < index : span.to > index) : order[maybe].level > span.level))
2101 maybe = i;
2102 }
2103 }
2104 if (maybe < 0)
2105 throw new RangeError("Index out of range");
2106 return maybe;
2107 }
2108}
2109// Reused array of character types
2110const types = [];
2111function computeOrder(line, direction) {
2112 let len = line.length, outerType = direction == LTR ? 1 /* L */ : 2 /* R */, oppositeType = direction == LTR ? 2 /* R */ : 1 /* L */;
2113 if (!line || outerType == 1 /* L */ && !BidiRE.test(line))
2114 return trivialOrder(len);
2115 // W1. Examine each non-spacing mark (NSM) in the level run, and
2116 // change the type of the NSM to the type of the previous
2117 // character. If the NSM is at the start of the level run, it will
2118 // get the type of sor.
2119 // W2. Search backwards from each instance of a European number
2120 // until the first strong type (R, L, AL, or sor) is found. If an
2121 // AL is found, change the type of the European number to Arabic
2122 // number.
2123 // W3. Change all ALs to R.
2124 // (Left after this: L, R, EN, AN, ET, CS, NI)
2125 for (let i = 0, prev = outerType, prevStrong = outerType; i < len; i++) {
2126 let type = charType(line.charCodeAt(i));
2127 if (type == 512 /* NSM */)
2128 type = prev;
2129 else if (type == 8 /* EN */ && prevStrong == 4 /* AL */)
2130 type = 16 /* AN */;
2131 types[i] = type == 4 /* AL */ ? 2 /* R */ : type;
2132 if (type & 7 /* Strong */)
2133 prevStrong = type;
2134 prev = type;
2135 }
2136 // W5. A sequence of European terminators adjacent to European
2137 // numbers changes to all European numbers.
2138 // W6. Otherwise, separators and terminators change to Other
2139 // Neutral.
2140 // W7. Search backwards from each instance of a European number
2141 // until the first strong type (R, L, or sor) is found. If an L is
2142 // found, then change the type of the European number to L.
2143 // (Left after this: L, R, EN+AN, NI)
2144 for (let i = 0, prev = outerType, prevStrong = outerType; i < len; i++) {
2145 let type = types[i];
2146 if (type == 128 /* CS */) {
2147 if (i < len - 1 && prev == types[i + 1] && (prev & 24 /* Num */))
2148 type = types[i] = prev;
2149 else
2150 types[i] = 256 /* NI */;
2151 }
2152 else if (type == 64 /* ET */) {
2153 let end = i + 1;
2154 while (end < len && types[end] == 64 /* ET */)
2155 end++;
2156 let replace = (i && prev == 8 /* EN */) || (end < len && types[end] == 8 /* EN */) ? (prevStrong == 1 /* L */ ? 1 /* L */ : 8 /* EN */) : 256 /* NI */;
2157 for (let j = i; j < end; j++)
2158 types[j] = replace;
2159 i = end - 1;
2160 }
2161 else if (type == 8 /* EN */ && prevStrong == 1 /* L */) {
2162 types[i] = 1 /* L */;
2163 }
2164 prev = type;
2165 if (type & 7 /* Strong */)
2166 prevStrong = type;
2167 }
2168 // N0. Process bracket pairs in an isolating run sequence
2169 // sequentially in the logical order of the text positions of the
2170 // opening paired brackets using the logic given below. Within this
2171 // scope, bidirectional types EN and AN are treated as R.
2172 for (let i = 0, sI = 0, context = 0, ch, br, type; i < len; i++) {
2173 // Keeps [startIndex, type, strongSeen] triples for each open
2174 // bracket on BracketStack.
2175 if (br = Brackets[ch = line.charCodeAt(i)]) {
2176 if (br < 0) { // Closing bracket
2177 for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
2178 if (BracketStack[sJ + 1] == -br) {
2179 let flags = BracketStack[sJ + 2];
2180 let type = (flags & 2 /* EmbedInside */) ? outerType :
2181 !(flags & 4 /* OppositeInside */) ? 0 :
2182 (flags & 1 /* OppositeBefore */) ? oppositeType : outerType;
2183 if (type)
2184 types[i] = types[BracketStack[sJ]] = type;
2185 sI = sJ;
2186 break;
2187 }
2188 }
2189 }
2190 else if (BracketStack.length == 189 /* MaxDepth */) {
2191 break;
2192 }
2193 else {
2194 BracketStack[sI++] = i;
2195 BracketStack[sI++] = ch;
2196 BracketStack[sI++] = context;
2197 }
2198 }
2199 else if ((type = types[i]) == 2 /* R */ || type == 1 /* L */) {
2200 let embed = type == outerType;
2201 context = embed ? 0 : 1 /* OppositeBefore */;
2202 for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
2203 let cur = BracketStack[sJ + 2];
2204 if (cur & 2 /* EmbedInside */)
2205 break;
2206 if (embed) {
2207 BracketStack[sJ + 2] |= 2 /* EmbedInside */;
2208 }
2209 else {
2210 if (cur & 4 /* OppositeInside */)
2211 break;
2212 BracketStack[sJ + 2] |= 4 /* OppositeInside */;
2213 }
2214 }
2215 }
2216 }
2217 // N1. A sequence of neutrals takes the direction of the
2218 // surrounding strong text if the text on both sides has the same
2219 // direction. European and Arabic numbers act as if they were R in
2220 // terms of their influence on neutrals. Start-of-level-run (sor)
2221 // and end-of-level-run (eor) are used at level run boundaries.
2222 // N2. Any remaining neutrals take the embedding direction.
2223 // (Left after this: L, R, EN+AN)
2224 for (let i = 0; i < len; i++) {
2225 if (types[i] == 256 /* NI */) {
2226 let end = i + 1;
2227 while (end < len && types[end] == 256 /* NI */)
2228 end++;
2229 let beforeL = (i ? types[i - 1] : outerType) == 1 /* L */;
2230 let afterL = (end < len ? types[end] : outerType) == 1 /* L */;
2231 let replace = beforeL == afterL ? (beforeL ? 1 /* L */ : 2 /* R */) : outerType;
2232 for (let j = i; j < end; j++)
2233 types[j] = replace;
2234 i = end - 1;
2235 }
2236 }
2237 // Here we depart from the documented algorithm, in order to avoid
2238 // building up an actual levels array. Since there are only three
2239 // levels (0, 1, 2) in an implementation that doesn't take
2240 // explicit embedding into account, we can build up the order on
2241 // the fly, without following the level-based algorithm.
2242 let order = [];
2243 if (outerType == 1 /* L */) {
2244 for (let i = 0; i < len;) {
2245 let start = i, rtl = types[i++] != 1 /* L */;
2246 while (i < len && rtl == (types[i] != 1 /* L */))
2247 i++;
2248 if (rtl) {
2249 for (let j = i; j > start;) {
2250 let end = j, l = types[--j] != 2 /* R */;
2251 while (j > start && l == (types[j - 1] != 2 /* R */))
2252 j--;
2253 order.push(new BidiSpan(j, end, l ? 2 : 1));
2254 }
2255 }
2256 else {
2257 order.push(new BidiSpan(start, i, 0));
2258 }
2259 }
2260 }
2261 else {
2262 for (let i = 0; i < len;) {
2263 let start = i, rtl = types[i++] == 2 /* R */;
2264 while (i < len && rtl == (types[i] == 2 /* R */))
2265 i++;
2266 order.push(new BidiSpan(start, i, rtl ? 1 : 2));
2267 }
2268 }
2269 return order;
2270}
2271function trivialOrder(length) {
2272 return [new BidiSpan(0, length, 0)];
2273}
2274let movedOver = "";
2275function moveVisually(line, order, dir, start, forward) {
2276 var _a;
2277 let startIndex = start.head - line.from, spanI = -1;
2278 if (startIndex == 0) {
2279 if (!forward || !line.length)
2280 return null;
2281 if (order[0].level != dir) {
2282 startIndex = order[0].side(false, dir);
2283 spanI = 0;
2284 }
2285 }
2286 else if (startIndex == line.length) {
2287 if (forward)
2288 return null;
2289 let last = order[order.length - 1];
2290 if (last.level != dir) {
2291 startIndex = last.side(true, dir);
2292 spanI = order.length - 1;
2293 }
2294 }
2295 if (spanI < 0)
2296 spanI = BidiSpan.find(order, startIndex, (_a = start.bidiLevel) !== null && _a !== void 0 ? _a : -1, start.assoc);
2297 let span = order[spanI];
2298 // End of span. (But not end of line--that was checked for above.)
2299 if (startIndex == span.side(forward, dir)) {
2300 span = order[spanI += forward ? 1 : -1];
2301 startIndex = span.side(!forward, dir);
2302 }
2303 let indexForward = forward == (span.dir == dir);
2304 let nextIndex = findClusterBreak(line.text, startIndex, indexForward);
2305 movedOver = line.text.slice(Math.min(startIndex, nextIndex), Math.max(startIndex, nextIndex));
2306 if (nextIndex != span.side(forward, dir))
2307 return EditorSelection.cursor(nextIndex + line.from, indexForward ? -1 : 1, span.level);
2308 let nextSpan = spanI == (forward ? order.length - 1 : 0) ? null : order[spanI + (forward ? 1 : -1)];
2309 if (!nextSpan && span.level != dir)
2310 return EditorSelection.cursor(forward ? line.to : line.from, forward ? -1 : 1, dir);
2311 if (nextSpan && nextSpan.level < span.level)
2312 return EditorSelection.cursor(nextSpan.side(!forward, dir) + line.from, forward ? 1 : -1, nextSpan.level);
2313 return EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
2314}
2315
2316const LineBreakPlaceholder = "\uffff";
2317class DOMReader {
2318 constructor(points, state) {
2319 this.points = points;
2320 this.text = "";
2321 this.lineSeparator = state.facet(EditorState.lineSeparator);
2322 }
2323 append(text) {
2324 this.text += text;
2325 }
2326 lineBreak() {
2327 this.text += LineBreakPlaceholder;
2328 }
2329 readRange(start, end) {
2330 if (!start)
2331 return this;
2332 let parent = start.parentNode;
2333 for (let cur = start;;) {
2334 this.findPointBefore(parent, cur);
2335 this.readNode(cur);
2336 let next = cur.nextSibling;
2337 if (next == end)
2338 break;
2339 let view = ContentView.get(cur), nextView = ContentView.get(next);
2340 if (view && nextView ? view.breakAfter :
2341 (view ? view.breakAfter : isBlockElement(cur)) ||
2342 (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
2343 this.lineBreak();
2344 cur = next;
2345 }
2346 this.findPointBefore(parent, end);
2347 return this;
2348 }
2349 readTextNode(node) {
2350 let text = node.nodeValue;
2351 for (let point of this.points)
2352 if (point.node == node)
2353 point.pos = this.text.length + Math.min(point.offset, text.length);
2354 for (let off = 0, re = this.lineSeparator ? null : /\r\n?|\n/g;;) {
2355 let nextBreak = -1, breakSize = 1, m;
2356 if (this.lineSeparator) {
2357 nextBreak = text.indexOf(this.lineSeparator, off);
2358 breakSize = this.lineSeparator.length;
2359 }
2360 else if (m = re.exec(text)) {
2361 nextBreak = m.index;
2362 breakSize = m[0].length;
2363 }
2364 this.append(text.slice(off, nextBreak < 0 ? text.length : nextBreak));
2365 if (nextBreak < 0)
2366 break;
2367 this.lineBreak();
2368 if (breakSize > 1)
2369 for (let point of this.points)
2370 if (point.node == node && point.pos > this.text.length)
2371 point.pos -= breakSize - 1;
2372 off = nextBreak + breakSize;
2373 }
2374 }
2375 readNode(node) {
2376 if (node.cmIgnore)
2377 return;
2378 let view = ContentView.get(node);
2379 let fromView = view && view.overrideDOMText;
2380 if (fromView != null) {
2381 this.findPointInside(node, fromView.length);
2382 for (let i = fromView.iter(); !i.next().done;) {
2383 if (i.lineBreak)
2384 this.lineBreak();
2385 else
2386 this.append(i.value);
2387 }
2388 }
2389 else if (node.nodeType == 3) {
2390 this.readTextNode(node);
2391 }
2392 else if (node.nodeName == "BR") {
2393 if (node.nextSibling)
2394 this.lineBreak();
2395 }
2396 else if (node.nodeType == 1) {
2397 this.readRange(node.firstChild, null);
2398 }
2399 }
2400 findPointBefore(node, next) {
2401 for (let point of this.points)
2402 if (point.node == node && node.childNodes[point.offset] == next)
2403 point.pos = this.text.length;
2404 }
2405 findPointInside(node, maxLen) {
2406 for (let point of this.points)
2407 if (node.nodeType == 3 ? point.node == node : node.contains(point.node))
2408 point.pos = this.text.length + Math.min(maxLen, point.offset);
2409 }
2410}
2411function isBlockElement(node) {
2412 return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
2413}
2414class DOMPoint {
2415 constructor(node, offset) {
2416 this.node = node;
2417 this.offset = offset;
2418 this.pos = -1;
2419 }
2420}
2421
2422class DocView extends ContentView {
2423 constructor(view) {
2424 super();
2425 this.view = view;
2426 this.compositionDeco = Decoration.none;
2427 this.decorations = [];
2428 this.dynamicDecorationMap = [];
2429 // Track a minimum width for the editor. When measuring sizes in
2430 // measureVisibleLineHeights, this is updated to point at the width
2431 // of a given element and its extent in the document. When a change
2432 // happens in that range, these are reset. That way, once we've seen
2433 // a line/element of a given length, we keep the editor wide enough
2434 // to fit at least that element, until it is changed, at which point
2435 // we forget it again.
2436 this.minWidth = 0;
2437 this.minWidthFrom = 0;
2438 this.minWidthTo = 0;
2439 // Track whether the DOM selection was set in a lossy way, so that
2440 // we don't mess it up when reading it back it
2441 this.impreciseAnchor = null;
2442 this.impreciseHead = null;
2443 this.forceSelection = false;
2444 // Used by the resize observer to ignore resizes that we caused
2445 // ourselves
2446 this.lastUpdate = Date.now();
2447 this.setDOM(view.contentDOM);
2448 this.children = [new LineView];
2449 this.children[0].setParent(this);
2450 this.updateDeco();
2451 this.updateInner([new ChangedRange(0, 0, 0, view.state.doc.length)], 0);
2452 }
2453 get root() { return this.view.root; }
2454 get editorView() { return this.view; }
2455 get length() { return this.view.state.doc.length; }
2456 // Update the document view to a given state. scrollIntoView can be
2457 // used as a hint to compute a new viewport that includes that
2458 // position, if we know the editor is going to scroll that position
2459 // into view.
2460 update(update) {
2461 let changedRanges = update.changedRanges;
2462 if (this.minWidth > 0 && changedRanges.length) {
2463 if (!changedRanges.every(({ fromA, toA }) => toA < this.minWidthFrom || fromA > this.minWidthTo)) {
2464 this.minWidth = this.minWidthFrom = this.minWidthTo = 0;
2465 }
2466 else {
2467 this.minWidthFrom = update.changes.mapPos(this.minWidthFrom, 1);
2468 this.minWidthTo = update.changes.mapPos(this.minWidthTo, 1);
2469 }
2470 }
2471 if (this.view.inputState.composing < 0)
2472 this.compositionDeco = Decoration.none;
2473 else if (update.transactions.length || this.dirty)
2474 this.compositionDeco = computeCompositionDeco(this.view, update.changes);
2475 // When the DOM nodes around the selection are moved to another
2476 // parent, Chrome sometimes reports a different selection through
2477 // getSelection than the one that it actually shows to the user.
2478 // This forces a selection update when lines are joined to work
2479 // around that. Issue #54
2480 if ((browser.ie || browser.chrome) && !this.compositionDeco.size && update &&
2481 update.state.doc.lines != update.startState.doc.lines)
2482 this.forceSelection = true;
2483 let prevDeco = this.decorations, deco = this.updateDeco();
2484 let decoDiff = findChangedDeco(prevDeco, deco, update.changes);
2485 changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
2486 if (this.dirty == 0 /* Not */ && changedRanges.length == 0) {
2487 return false;
2488 }
2489 else {
2490 this.updateInner(changedRanges, update.startState.doc.length);
2491 if (update.transactions.length)
2492 this.lastUpdate = Date.now();
2493 return true;
2494 }
2495 }
2496 // Used by update and the constructor do perform the actual DOM
2497 // update
2498 updateInner(changes, oldLength) {
2499 this.view.viewState.mustMeasureContent = true;
2500 this.updateChildren(changes, oldLength);
2501 let { observer } = this.view;
2502 observer.ignore(() => {
2503 // Lock the height during redrawing, since Chrome sometimes
2504 // messes with the scroll position during DOM mutation (though
2505 // no relayout is triggered and I cannot imagine how it can
2506 // recompute the scroll position without a layout)
2507 this.dom.style.height = this.view.viewState.contentHeight + "px";
2508 this.dom.style.flexBasis = this.minWidth ? this.minWidth + "px" : "";
2509 // Chrome will sometimes, when DOM mutations occur directly
2510 // around the selection, get confused and report a different
2511 // selection from the one it displays (issue #218). This tries
2512 // to detect that situation.
2513 let track = browser.chrome || browser.ios ? { node: observer.selectionRange.focusNode, written: false } : undefined;
2514 this.sync(track);
2515 this.dirty = 0 /* Not */;
2516 if (track && (track.written || observer.selectionRange.focusNode != track.node))
2517 this.forceSelection = true;
2518 this.dom.style.height = "";
2519 });
2520 let gaps = [];
2521 if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
2522 for (let child of this.children)
2523 if (child instanceof BlockWidgetView && child.widget instanceof BlockGapWidget)
2524 gaps.push(child.dom);
2525 observer.updateGaps(gaps);
2526 }
2527 updateChildren(changes, oldLength) {
2528 let cursor = this.childCursor(oldLength);
2529 for (let i = changes.length - 1;; i--) {
2530 let next = i >= 0 ? changes[i] : null;
2531 if (!next)
2532 break;
2533 let { fromA, toA, fromB, toB } = next;
2534 let { content, breakAtStart, openStart, openEnd } = ContentBuilder.build(this.view.state.doc, fromB, toB, this.decorations, this.dynamicDecorationMap);
2535 let { i: toI, off: toOff } = cursor.findPos(toA, 1);
2536 let { i: fromI, off: fromOff } = cursor.findPos(fromA, -1);
2537 replaceRange(this, fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd);
2538 }
2539 }
2540 // Sync the DOM selection to this.state.selection
2541 updateSelection(mustRead = false, fromPointer = false) {
2542 if (mustRead || !this.view.observer.selectionRange.focusNode)
2543 this.view.observer.readSelectionRange();
2544 if (!(fromPointer || this.mayControlSelection()) ||
2545 browser.ios && this.view.inputState.rapidCompositionStart)
2546 return;
2547 let force = this.forceSelection;
2548 this.forceSelection = false;
2549 let main = this.view.state.selection.main;
2550 // FIXME need to handle the case where the selection falls inside a block range
2551 let anchor = this.domAtPos(main.anchor);
2552 let head = main.empty ? anchor : this.domAtPos(main.head);
2553 // Always reset on Firefox when next to an uneditable node to
2554 // avoid invisible cursor bugs (#111)
2555 if (browser.gecko && main.empty && betweenUneditable(anchor)) {
2556 let dummy = document.createTextNode("");
2557 this.view.observer.ignore(() => anchor.node.insertBefore(dummy, anchor.node.childNodes[anchor.offset] || null));
2558 anchor = head = new DOMPos(dummy, 0);
2559 force = true;
2560 }
2561 let domSel = this.view.observer.selectionRange;
2562 // If the selection is already here, or in an equivalent position, don't touch it
2563 if (force || !domSel.focusNode ||
2564 !isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
2565 !isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
2566 this.view.observer.ignore(() => {
2567 // Chrome Android will hide the virtual keyboard when tapping
2568 // inside an uneditable node, and not bring it back when we
2569 // move the cursor to its proper position. This tries to
2570 // restore the keyboard by cycling focus.
2571 if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) &&
2572 inUneditable(domSel.focusNode, this.dom)) {
2573 this.dom.blur();
2574 this.dom.focus({ preventScroll: true });
2575 }
2576 let rawSel = getSelection(this.root);
2577 if (!rawSel) ;
2578 else if (main.empty) {
2579 // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
2580 if (browser.gecko) {
2581 let nextTo = nextToUneditable(anchor.node, anchor.offset);
2582 if (nextTo && nextTo != (1 /* Before */ | 2 /* After */)) {
2583 let text = nearbyTextNode(anchor.node, anchor.offset, nextTo == 1 /* Before */ ? 1 : -1);
2584 if (text)
2585 anchor = new DOMPos(text, nextTo == 1 /* Before */ ? 0 : text.nodeValue.length);
2586 }
2587 }
2588 rawSel.collapse(anchor.node, anchor.offset);
2589 if (main.bidiLevel != null && domSel.cursorBidiLevel != null)
2590 domSel.cursorBidiLevel = main.bidiLevel;
2591 }
2592 else if (rawSel.extend) {
2593 // Selection.extend can be used to create an 'inverted' selection
2594 // (one where the focus is before the anchor), but not all
2595 // browsers support it yet.
2596 rawSel.collapse(anchor.node, anchor.offset);
2597 rawSel.extend(head.node, head.offset);
2598 }
2599 else {
2600 // Primitive (IE) way
2601 let range = document.createRange();
2602 if (main.anchor > main.head)
2603 [anchor, head] = [head, anchor];
2604 range.setEnd(head.node, head.offset);
2605 range.setStart(anchor.node, anchor.offset);
2606 rawSel.removeAllRanges();
2607 rawSel.addRange(range);
2608 }
2609 });
2610 this.view.observer.setSelectionRange(anchor, head);
2611 }
2612 this.impreciseAnchor = anchor.precise ? null : new DOMPos(domSel.anchorNode, domSel.anchorOffset);
2613 this.impreciseHead = head.precise ? null : new DOMPos(domSel.focusNode, domSel.focusOffset);
2614 }
2615 enforceCursorAssoc() {
2616 if (this.compositionDeco.size)
2617 return;
2618 let cursor = this.view.state.selection.main;
2619 let sel = getSelection(this.root);
2620 if (!sel || !cursor.empty || !cursor.assoc || !sel.modify)
2621 return;
2622 let line = LineView.find(this, cursor.head);
2623 if (!line)
2624 return;
2625 let lineStart = line.posAtStart;
2626 if (cursor.head == lineStart || cursor.head == lineStart + line.length)
2627 return;
2628 let before = this.coordsAt(cursor.head, -1), after = this.coordsAt(cursor.head, 1);
2629 if (!before || !after || before.bottom > after.top)
2630 return;
2631 let dom = this.domAtPos(cursor.head + cursor.assoc);
2632 sel.collapse(dom.node, dom.offset);
2633 sel.modify("move", cursor.assoc < 0 ? "forward" : "backward", "lineboundary");
2634 }
2635 mayControlSelection() {
2636 let active = this.root.activeElement;
2637 return active == this.dom ||
2638 hasSelection(this.dom, this.view.observer.selectionRange) && !(active && this.dom.contains(active));
2639 }
2640 nearest(dom) {
2641 for (let cur = dom; cur;) {
2642 let domView = ContentView.get(cur);
2643 if (domView && domView.rootView == this)
2644 return domView;
2645 cur = cur.parentNode;
2646 }
2647 return null;
2648 }
2649 posFromDOM(node, offset) {
2650 let view = this.nearest(node);
2651 if (!view)
2652 throw new RangeError("Trying to find position for a DOM position outside of the document");
2653 return view.localPosFromDOM(node, offset) + view.posAtStart;
2654 }
2655 domAtPos(pos) {
2656 let { i, off } = this.childCursor().findPos(pos, -1);
2657 for (; i < this.children.length - 1;) {
2658 let child = this.children[i];
2659 if (off < child.length || child instanceof LineView)
2660 break;
2661 i++;
2662 off = 0;
2663 }
2664 return this.children[i].domAtPos(off);
2665 }
2666 coordsAt(pos, side) {
2667 for (let off = this.length, i = this.children.length - 1;; i--) {
2668 let child = this.children[i], start = off - child.breakAfter - child.length;
2669 if (pos > start ||
2670 (pos == start && child.type != BlockType.WidgetBefore && child.type != BlockType.WidgetAfter &&
2671 (!i || side == 2 || this.children[i - 1].breakAfter ||
2672 (this.children[i - 1].type == BlockType.WidgetBefore && side > -2))))
2673 return child.coordsAt(pos - start, side);
2674 off = start;
2675 }
2676 }
2677 measureVisibleLineHeights(viewport) {
2678 let result = [], { from, to } = viewport;
2679 let contentWidth = this.view.contentDOM.clientWidth;
2680 let isWider = contentWidth > Math.max(this.view.scrollDOM.clientWidth, this.minWidth) + 1;
2681 let widest = -1, ltr = this.view.textDirection == Direction.LTR;
2682 for (let pos = 0, i = 0; i < this.children.length; i++) {
2683 let child = this.children[i], end = pos + child.length;
2684 if (end > to)
2685 break;
2686 if (pos >= from) {
2687 let childRect = child.dom.getBoundingClientRect();
2688 result.push(childRect.height);
2689 if (isWider) {
2690 let last = child.dom.lastChild;
2691 let rects = last ? clientRectsFor(last) : [];
2692 if (rects.length) {
2693 let rect = rects[rects.length - 1];
2694 let width = ltr ? rect.right - childRect.left : childRect.right - rect.left;
2695 if (width > widest) {
2696 widest = width;
2697 this.minWidth = contentWidth;
2698 this.minWidthFrom = pos;
2699 this.minWidthTo = end;
2700 }
2701 }
2702 }
2703 }
2704 pos = end + child.breakAfter;
2705 }
2706 return result;
2707 }
2708 textDirectionAt(pos) {
2709 let { i } = this.childPos(pos, 1);
2710 return getComputedStyle(this.children[i].dom).direction == "rtl" ? Direction.RTL : Direction.LTR;
2711 }
2712 measureTextSize() {
2713 for (let child of this.children) {
2714 if (child instanceof LineView) {
2715 let measure = child.measureTextSize();
2716 if (measure)
2717 return measure;
2718 }
2719 }
2720 // If no workable line exists, force a layout of a measurable element
2721 let dummy = document.createElement("div"), lineHeight, charWidth;
2722 dummy.className = "cm-line";
2723 dummy.textContent = "abc def ghi jkl mno pqr stu";
2724 this.view.observer.ignore(() => {
2725 this.dom.appendChild(dummy);
2726 let rect = clientRectsFor(dummy.firstChild)[0];
2727 lineHeight = dummy.getBoundingClientRect().height;
2728 charWidth = rect ? rect.width / 27 : 7;
2729 dummy.remove();
2730 });
2731 return { lineHeight, charWidth };
2732 }
2733 childCursor(pos = this.length) {
2734 // Move back to start of last element when possible, so that
2735 // `ChildCursor.findPos` doesn't have to deal with the edge case
2736 // of being after the last element.
2737 let i = this.children.length;
2738 if (i)
2739 pos -= this.children[--i].length;
2740 return new ChildCursor(this.children, pos, i);
2741 }
2742 computeBlockGapDeco() {
2743 let deco = [], vs = this.view.viewState;
2744 for (let pos = 0, i = 0;; i++) {
2745 let next = i == vs.viewports.length ? null : vs.viewports[i];
2746 let end = next ? next.from - 1 : this.length;
2747 if (end > pos) {
2748 let height = vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top;
2749 deco.push(Decoration.replace({
2750 widget: new BlockGapWidget(height),
2751 block: true,
2752 inclusive: true,
2753 isBlockGap: true,
2754 }).range(pos, end));
2755 }
2756 if (!next)
2757 break;
2758 pos = next.to + 1;
2759 }
2760 return Decoration.set(deco);
2761 }
2762 updateDeco() {
2763 let allDeco = this.view.state.facet(decorations).map((d, i) => {
2764 let dynamic = this.dynamicDecorationMap[i] = typeof d == "function";
2765 return dynamic ? d(this.view) : d;
2766 });
2767 for (let i = allDeco.length; i < allDeco.length + 3; i++)
2768 this.dynamicDecorationMap[i] = false;
2769 return this.decorations = [
2770 ...allDeco,
2771 this.compositionDeco,
2772 this.computeBlockGapDeco(),
2773 this.view.viewState.lineGapDeco
2774 ];
2775 }
2776 scrollIntoView(target) {
2777 let { range } = target;
2778 let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
2779 if (!rect)
2780 return;
2781 if (!range.empty && (other = this.coordsAt(range.anchor, range.anchor > range.head ? -1 : 1)))
2782 rect = { left: Math.min(rect.left, other.left), top: Math.min(rect.top, other.top),
2783 right: Math.max(rect.right, other.right), bottom: Math.max(rect.bottom, other.bottom) };
2784 let mLeft = 0, mRight = 0, mTop = 0, mBottom = 0;
2785 for (let margins of this.view.state.facet(scrollMargins).map(f => f(this.view)))
2786 if (margins) {
2787 let { left, right, top, bottom } = margins;
2788 if (left != null)
2789 mLeft = Math.max(mLeft, left);
2790 if (right != null)
2791 mRight = Math.max(mRight, right);
2792 if (top != null)
2793 mTop = Math.max(mTop, top);
2794 if (bottom != null)
2795 mBottom = Math.max(mBottom, bottom);
2796 }
2797 let targetRect = {
2798 left: rect.left - mLeft, top: rect.top - mTop,
2799 right: rect.right + mRight, bottom: rect.bottom + mBottom
2800 };
2801 scrollRectIntoView(this.view.scrollDOM, targetRect, range.head < range.anchor ? -1 : 1, target.x, target.y, target.xMargin, target.yMargin, this.view.textDirection == Direction.LTR);
2802 }
2803}
2804function betweenUneditable(pos) {
2805 return pos.node.nodeType == 1 && pos.node.firstChild &&
2806 (pos.offset == 0 || pos.node.childNodes[pos.offset - 1].contentEditable == "false") &&
2807 (pos.offset == pos.node.childNodes.length || pos.node.childNodes[pos.offset].contentEditable == "false");
2808}
2809class BlockGapWidget extends WidgetType {
2810 constructor(height) {
2811 super();
2812 this.height = height;
2813 }
2814 toDOM() {
2815 let elt = document.createElement("div");
2816 this.updateDOM(elt);
2817 return elt;
2818 }
2819 eq(other) { return other.height == this.height; }
2820 updateDOM(elt) {
2821 elt.style.height = this.height + "px";
2822 return true;
2823 }
2824 get estimatedHeight() { return this.height; }
2825}
2826function compositionSurroundingNode(view) {
2827 let sel = view.observer.selectionRange;
2828 let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
2829 if (!textNode)
2830 return null;
2831 let cView = view.docView.nearest(textNode);
2832 if (!cView)
2833 return null;
2834 if (cView instanceof LineView) {
2835 let topNode = textNode;
2836 while (topNode.parentNode != cView.dom)
2837 topNode = topNode.parentNode;
2838 let prev = topNode.previousSibling;
2839 while (prev && !ContentView.get(prev))
2840 prev = prev.previousSibling;
2841 let pos = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2842 return { from: pos, to: pos, node: topNode, text: textNode };
2843 }
2844 else {
2845 for (;;) {
2846 let { parent } = cView;
2847 if (!parent)
2848 return null;
2849 if (parent instanceof LineView)
2850 break;
2851 cView = parent;
2852 }
2853 let from = cView.posAtStart;
2854 return { from, to: from + cView.length, node: cView.dom, text: textNode };
2855 }
2856}
2857function computeCompositionDeco(view, changes) {
2858 let surrounding = compositionSurroundingNode(view);
2859 if (!surrounding)
2860 return Decoration.none;
2861 let { from, to, node, text: textNode } = surrounding;
2862 let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2863 let { state } = view, text = node.nodeType == 3 ? node.nodeValue :
2864 new DOMReader([], state).readRange(node.firstChild, null).text;
2865 if (newTo - newFrom < text.length) {
2866 if (state.doc.sliceString(newFrom, Math.min(state.doc.length, newFrom + text.length), LineBreakPlaceholder) == text)
2867 newTo = newFrom + text.length;
2868 else if (state.doc.sliceString(Math.max(0, newTo - text.length), newTo, LineBreakPlaceholder) == text)
2869 newFrom = newTo - text.length;
2870 else
2871 return Decoration.none;
2872 }
2873 else if (state.doc.sliceString(newFrom, newTo, LineBreakPlaceholder) != text) {
2874 return Decoration.none;
2875 }
2876 let topView = ContentView.get(node);
2877 if (topView instanceof CompositionView)
2878 topView = topView.widget.topView;
2879 else if (topView)
2880 topView.parent = null;
2881 return Decoration.set(Decoration.replace({ widget: new CompositionWidget(node, textNode, topView), inclusive: true })
2882 .range(newFrom, newTo));
2883}
2884class CompositionWidget extends WidgetType {
2885 constructor(top, text, topView) {
2886 super();
2887 this.top = top;
2888 this.text = text;
2889 this.topView = topView;
2890 }
2891 eq(other) { return this.top == other.top && this.text == other.text; }
2892 toDOM() { return this.top; }
2893 ignoreEvent() { return false; }
2894 get customView() { return CompositionView; }
2895}
2896function nearbyTextNode(node, offset, side) {
2897 for (;;) {
2898 if (node.nodeType == 3)
2899 return node;
2900 if (node.nodeType == 1 && offset > 0 && side <= 0) {
2901 node = node.childNodes[offset - 1];
2902 offset = maxOffset(node);
2903 }
2904 else if (node.nodeType == 1 && offset < node.childNodes.length && side >= 0) {
2905 node = node.childNodes[offset];
2906 offset = 0;
2907 }
2908 else {
2909 return null;
2910 }
2911 }
2912}
2913function nextToUneditable(node, offset) {
2914 if (node.nodeType != 1)
2915 return 0;
2916 return (offset && node.childNodes[offset - 1].contentEditable == "false" ? 1 /* Before */ : 0) |
2917 (offset < node.childNodes.length && node.childNodes[offset].contentEditable == "false" ? 2 /* After */ : 0);
2918}
2919class DecorationComparator$1 {
2920 constructor() {
2921 this.changes = [];
2922 }
2923 compareRange(from, to) { addRange(from, to, this.changes); }
2924 comparePoint(from, to) { addRange(from, to, this.changes); }
2925}
2926function findChangedDeco(a, b, diff) {
2927 let comp = new DecorationComparator$1;
2928 RangeSet.compare(a, b, diff, comp);
2929 return comp.changes;
2930}
2931function inUneditable(node, inside) {
2932 for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
2933 if (cur.nodeType == 1 && cur.contentEditable == 'false') {
2934 return true;
2935 }
2936 }
2937 return false;
2938}
2939
2940function groupAt(state, pos, bias = 1) {
2941 let categorize = state.charCategorizer(pos);
2942 let line = state.doc.lineAt(pos), linePos = pos - line.from;
2943 if (line.length == 0)
2944 return EditorSelection.cursor(pos);
2945 if (linePos == 0)
2946 bias = 1;
2947 else if (linePos == line.length)
2948 bias = -1;
2949 let from = linePos, to = linePos;
2950 if (bias < 0)
2951 from = findClusterBreak(line.text, linePos, false);
2952 else
2953 to = findClusterBreak(line.text, linePos);
2954 let cat = categorize(line.text.slice(from, to));
2955 while (from > 0) {
2956 let prev = findClusterBreak(line.text, from, false);
2957 if (categorize(line.text.slice(prev, from)) != cat)
2958 break;
2959 from = prev;
2960 }
2961 while (to < line.length) {
2962 let next = findClusterBreak(line.text, to);
2963 if (categorize(line.text.slice(to, next)) != cat)
2964 break;
2965 to = next;
2966 }
2967 return EditorSelection.range(from + line.from, to + line.from);
2968}
2969// Search the DOM for the {node, offset} position closest to the given
2970// coordinates. Very inefficient and crude, but can usually be avoided
2971// by calling caret(Position|Range)FromPoint instead.
2972function getdx(x, rect) {
2973 return rect.left > x ? rect.left - x : Math.max(0, x - rect.right);
2974}
2975function getdy(y, rect) {
2976 return rect.top > y ? rect.top - y : Math.max(0, y - rect.bottom);
2977}
2978function yOverlap(a, b) {
2979 return a.top < b.bottom - 1 && a.bottom > b.top + 1;
2980}
2981function upTop(rect, top) {
2982 return top < rect.top ? { top, left: rect.left, right: rect.right, bottom: rect.bottom } : rect;
2983}
2984function upBot(rect, bottom) {
2985 return bottom > rect.bottom ? { top: rect.top, left: rect.left, right: rect.right, bottom } : rect;
2986}
2987function domPosAtCoords(parent, x, y) {
2988 let closest, closestRect, closestX, closestY;
2989 let above, below, aboveRect, belowRect;
2990 for (let child = parent.firstChild; child; child = child.nextSibling) {
2991 let rects = clientRectsFor(child);
2992 for (let i = 0; i < rects.length; i++) {
2993 let rect = rects[i];
2994 if (closestRect && yOverlap(closestRect, rect))
2995 rect = upTop(upBot(rect, closestRect.bottom), closestRect.top);
2996 let dx = getdx(x, rect), dy = getdy(y, rect);
2997 if (dx == 0 && dy == 0)
2998 return child.nodeType == 3 ? domPosInText(child, x, y) : domPosAtCoords(child, x, y);
2999 if (!closest || closestY > dy || closestY == dy && closestX > dx) {
3000 closest = child;
3001 closestRect = rect;
3002 closestX = dx;
3003 closestY = dy;
3004 }
3005 if (dx == 0) {
3006 if (y > rect.bottom && (!aboveRect || aboveRect.bottom < rect.bottom)) {
3007 above = child;
3008 aboveRect = rect;
3009 }
3010 else if (y < rect.top && (!belowRect || belowRect.top > rect.top)) {
3011 below = child;
3012 belowRect = rect;
3013 }
3014 }
3015 else if (aboveRect && yOverlap(aboveRect, rect)) {
3016 aboveRect = upBot(aboveRect, rect.bottom);
3017 }
3018 else if (belowRect && yOverlap(belowRect, rect)) {
3019 belowRect = upTop(belowRect, rect.top);
3020 }
3021 }
3022 }
3023 if (aboveRect && aboveRect.bottom >= y) {
3024 closest = above;
3025 closestRect = aboveRect;
3026 }
3027 else if (belowRect && belowRect.top <= y) {
3028 closest = below;
3029 closestRect = belowRect;
3030 }
3031 if (!closest)
3032 return { node: parent, offset: 0 };
3033 let clipX = Math.max(closestRect.left, Math.min(closestRect.right, x));
3034 if (closest.nodeType == 3)
3035 return domPosInText(closest, clipX, y);
3036 if (!closestX && closest.contentEditable == "true")
3037 return domPosAtCoords(closest, clipX, y);
3038 let offset = Array.prototype.indexOf.call(parent.childNodes, closest) +
3039 (x >= (closestRect.left + closestRect.right) / 2 ? 1 : 0);
3040 return { node: parent, offset };
3041}
3042function domPosInText(node, x, y) {
3043 let len = node.nodeValue.length;
3044 let closestOffset = -1, closestDY = 1e9, generalSide = 0;
3045 for (let i = 0; i < len; i++) {
3046 let rects = textRange(node, i, i + 1).getClientRects();
3047 for (let j = 0; j < rects.length; j++) {
3048 let rect = rects[j];
3049 if (rect.top == rect.bottom)
3050 continue;
3051 if (!generalSide)
3052 generalSide = x - rect.left;
3053 let dy = (rect.top > y ? rect.top - y : y - rect.bottom) - 1;
3054 if (rect.left - 1 <= x && rect.right + 1 >= x && dy < closestDY) {
3055 let right = x >= (rect.left + rect.right) / 2, after = right;
3056 if (browser.chrome || browser.gecko) {
3057 // Check for RTL on browsers that support getting client
3058 // rects for empty ranges.
3059 let rectBefore = textRange(node, i).getBoundingClientRect();
3060 if (rectBefore.left == rect.right)
3061 after = !right;
3062 }
3063 if (dy <= 0)
3064 return { node, offset: i + (after ? 1 : 0) };
3065 closestOffset = i + (after ? 1 : 0);
3066 closestDY = dy;
3067 }
3068 }
3069 }
3070 return { node, offset: closestOffset > -1 ? closestOffset : generalSide > 0 ? node.nodeValue.length : 0 };
3071}
3072function posAtCoords(view, { x, y }, precise, bias = -1) {
3073 var _a;
3074 let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
3075 let block, { docHeight } = view.viewState;
3076 let yOffset = y - docTop;
3077 if (yOffset < 0)
3078 return 0;
3079 if (yOffset > docHeight)
3080 return view.state.doc.length;
3081 // Scan for a text block near the queried y position
3082 for (let halfLine = view.defaultLineHeight / 2, bounced = false;;) {
3083 block = view.elementAtHeight(yOffset);
3084 if (block.type == BlockType.Text)
3085 break;
3086 for (;;) {
3087 // Move the y position out of this block
3088 yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
3089 if (yOffset >= 0 && yOffset <= docHeight)
3090 break;
3091 // If the document consists entirely of replaced widgets, we
3092 // won't find a text block, so return 0
3093 if (bounced)
3094 return precise ? null : 0;
3095 bounced = true;
3096 bias = -bias;
3097 }
3098 }
3099 y = docTop + yOffset;
3100 let lineStart = block.from;
3101 // If this is outside of the rendered viewport, we can't determine a position
3102 if (lineStart < view.viewport.from)
3103 return view.viewport.from == 0 ? 0 : precise ? null : posAtCoordsImprecise(view, content, block, x, y);
3104 if (lineStart > view.viewport.to)
3105 return view.viewport.to == view.state.doc.length ? view.state.doc.length :
3106 precise ? null : posAtCoordsImprecise(view, content, block, x, y);
3107 // Prefer ShadowRootOrDocument.elementFromPoint if present, fall back to document if not
3108 let doc = view.dom.ownerDocument;
3109 let root = view.root.elementFromPoint ? view.root : doc;
3110 let element = root.elementFromPoint(x, y);
3111 if (element && !view.contentDOM.contains(element))
3112 element = null;
3113 // If the element is unexpected, clip x at the sides of the content area and try again
3114 if (!element) {
3115 x = Math.max(content.left + 1, Math.min(content.right - 1, x));
3116 element = root.elementFromPoint(x, y);
3117 if (element && !view.contentDOM.contains(element))
3118 element = null;
3119 }
3120 // There's visible editor content under the point, so we can try
3121 // using caret(Position|Range)FromPoint as a shortcut
3122 let node, offset = -1;
3123 if (element && ((_a = view.docView.nearest(element)) === null || _a === void 0 ? void 0 : _a.isEditable) != false) {
3124 if (doc.caretPositionFromPoint) {
3125 let pos = doc.caretPositionFromPoint(x, y);
3126 if (pos)
3127 ({ offsetNode: node, offset } = pos);
3128 }
3129 else if (doc.caretRangeFromPoint) {
3130 let range = doc.caretRangeFromPoint(x, y);
3131 if (range) {
3132 ({ startContainer: node, startOffset: offset } = range);
3133 if (browser.safari && isSuspiciousCaretResult(node, offset, x))
3134 node = undefined;
3135 }
3136 }
3137 }
3138 // No luck, do our own (potentially expensive) search
3139 if (!node || !view.docView.dom.contains(node)) {
3140 let line = LineView.find(view.docView, lineStart);
3141 if (!line)
3142 return yOffset > block.top + block.height / 2 ? block.to : block.from;
3143 ({ node, offset } = domPosAtCoords(line.dom, x, y));
3144 }
3145 return view.docView.posFromDOM(node, offset);
3146}
3147function posAtCoordsImprecise(view, contentRect, block, x, y) {
3148 let into = Math.round((x - contentRect.left) * view.defaultCharacterWidth);
3149 if (view.lineWrapping && block.height > view.defaultLineHeight * 1.5) {
3150 let line = Math.floor((y - block.top) / view.defaultLineHeight);
3151 into += line * view.viewState.heightOracle.lineLength;
3152 }
3153 let content = view.state.sliceDoc(block.from, block.to);
3154 return block.from + findColumn(content, into, view.state.tabSize);
3155}
3156// In case of a high line height, Safari's caretRangeFromPoint treats
3157// the space between lines as belonging to the last character of the
3158// line before. This is used to detect such a result so that it can be
3159// ignored (issue #401).
3160function isSuspiciousCaretResult(node, offset, x) {
3161 let len;
3162 if (node.nodeType != 3 || offset != (len = node.nodeValue.length))
3163 return false;
3164 for (let next = node.nextSibling; next; next = next.nextSibling)
3165 if (next.nodeType != 1 || next.nodeName != "BR")
3166 return false;
3167 return textRange(node, len - 1, len).getBoundingClientRect().left > x;
3168}
3169function moveToLineBoundary(view, start, forward, includeWrap) {
3170 let line = view.state.doc.lineAt(start.head);
3171 let coords = !includeWrap || !view.lineWrapping ? null
3172 : view.coordsAtPos(start.assoc < 0 && start.head > line.from ? start.head - 1 : start.head);
3173 if (coords) {
3174 let editorRect = view.dom.getBoundingClientRect();
3175 let direction = view.textDirectionAt(line.from);
3176 let pos = view.posAtCoords({ x: forward == (direction == Direction.LTR) ? editorRect.right - 1 : editorRect.left + 1,
3177 y: (coords.top + coords.bottom) / 2 });
3178 if (pos != null)
3179 return EditorSelection.cursor(pos, forward ? -1 : 1);
3180 }
3181 let lineView = LineView.find(view.docView, start.head);
3182 let end = lineView ? (forward ? lineView.posAtEnd : lineView.posAtStart) : (forward ? line.to : line.from);
3183 return EditorSelection.cursor(end, forward ? -1 : 1);
3184}
3185function moveByChar(view, start, forward, by) {
3186 let line = view.state.doc.lineAt(start.head), spans = view.bidiSpans(line);
3187 let direction = view.textDirectionAt(line.from);
3188 for (let cur = start, check = null;;) {
3189 let next = moveVisually(line, spans, direction, cur, forward), char = movedOver;
3190 if (!next) {
3191 if (line.number == (forward ? view.state.doc.lines : 1))
3192 return cur;
3193 char = "\n";
3194 line = view.state.doc.line(line.number + (forward ? 1 : -1));
3195 spans = view.bidiSpans(line);
3196 next = EditorSelection.cursor(forward ? line.from : line.to);
3197 }
3198 if (!check) {
3199 if (!by)
3200 return next;
3201 check = by(char);
3202 }
3203 else if (!check(char)) {
3204 return cur;
3205 }
3206 cur = next;
3207 }
3208}
3209function byGroup(view, pos, start) {
3210 let categorize = view.state.charCategorizer(pos);
3211 let cat = categorize(start);
3212 return (next) => {
3213 let nextCat = categorize(next);
3214 if (cat == CharCategory.Space)
3215 cat = nextCat;
3216 return cat == nextCat;
3217 };
3218}
3219function moveVertically(view, start, forward, distance) {
3220 let startPos = start.head, dir = forward ? 1 : -1;
3221 if (startPos == (forward ? view.state.doc.length : 0))
3222 return EditorSelection.cursor(startPos, start.assoc);
3223 let goal = start.goalColumn, startY;
3224 let rect = view.contentDOM.getBoundingClientRect();
3225 let startCoords = view.coordsAtPos(startPos), docTop = view.documentTop;
3226 if (startCoords) {
3227 if (goal == null)
3228 goal = startCoords.left - rect.left;
3229 startY = dir < 0 ? startCoords.top : startCoords.bottom;
3230 }
3231 else {
3232 let line = view.viewState.lineBlockAt(startPos);
3233 if (goal == null)
3234 goal = Math.min(rect.right - rect.left, view.defaultCharacterWidth * (startPos - line.from));
3235 startY = (dir < 0 ? line.top : line.bottom) + docTop;
3236 }
3237 let resolvedGoal = rect.left + goal;
3238 let dist = distance !== null && distance !== void 0 ? distance : (view.defaultLineHeight >> 1);
3239 for (let extra = 0;; extra += 10) {
3240 let curY = startY + (dist + extra) * dir;
3241 let pos = posAtCoords(view, { x: resolvedGoal, y: curY }, false, dir);
3242 if (curY < rect.top || curY > rect.bottom || (dir < 0 ? pos < startPos : pos > startPos))
3243 return EditorSelection.cursor(pos, start.assoc, undefined, goal);
3244 }
3245}
3246function skipAtoms(view, oldPos, pos) {
3247 let atoms = view.state.facet(atomicRanges).map(f => f(view));
3248 for (;;) {
3249 let moved = false;
3250 for (let set of atoms) {
3251 set.between(pos.from - 1, pos.from + 1, (from, to, value) => {
3252 if (pos.from > from && pos.from < to) {
3253 pos = oldPos.from > pos.from ? EditorSelection.cursor(from, 1) : EditorSelection.cursor(to, -1);
3254 moved = true;
3255 }
3256 });
3257 }
3258 if (!moved)
3259 return pos;
3260 }
3261}
3262
3263// This will also be where dragging info and such goes
3264class InputState {
3265 constructor(view) {
3266 this.lastKeyCode = 0;
3267 this.lastKeyTime = 0;
3268 this.chromeScrollHack = -1;
3269 // On iOS, some keys need to have their default behavior happen
3270 // (after which we retroactively handle them and reset the DOM) to
3271 // avoid messing up the virtual keyboard state.
3272 this.pendingIOSKey = undefined;
3273 this.lastSelectionOrigin = null;
3274 this.lastSelectionTime = 0;
3275 this.lastEscPress = 0;
3276 this.lastContextMenu = 0;
3277 this.scrollHandlers = [];
3278 this.registeredEvents = [];
3279 this.customHandlers = [];
3280 // -1 means not in a composition. Otherwise, this counts the number
3281 // of changes made during the composition. The count is used to
3282 // avoid treating the start state of the composition, before any
3283 // changes have been made, as part of the composition.
3284 this.composing = -1;
3285 // Tracks whether the next change should be marked as starting the
3286 // composition (null means no composition, true means next is the
3287 // first, false means first has already been marked for this
3288 // composition)
3289 this.compositionFirstChange = null;
3290 this.compositionEndedAt = 0;
3291 this.rapidCompositionStart = false;
3292 this.mouseSelection = null;
3293 for (let type in handlers) {
3294 let handler = handlers[type];
3295 view.contentDOM.addEventListener(type, (event) => {
3296 if (!eventBelongsToEditor(view, event) || this.ignoreDuringComposition(event))
3297 return;
3298 if (type == "keydown" && this.keydown(view, event))
3299 return;
3300 if (this.mustFlushObserver(event))
3301 view.observer.forceFlush();
3302 if (this.runCustomHandlers(type, view, event))
3303 event.preventDefault();
3304 else
3305 handler(view, event);
3306 });
3307 this.registeredEvents.push(type);
3308 }
3309 if (browser.chrome && browser.chrome_version >= 102) {
3310 // On Chrome 102, viewport updates somehow stop wheel-based
3311 // scrolling. Turning off pointer events during the scroll seems
3312 // to avoid the issue.
3313 view.scrollDOM.addEventListener("wheel", () => {
3314 if (this.chromeScrollHack < 0)
3315 view.contentDOM.style.pointerEvents = "none";
3316 else
3317 window.clearTimeout(this.chromeScrollHack);
3318 this.chromeScrollHack = setTimeout(() => {
3319 this.chromeScrollHack = -1;
3320 view.contentDOM.style.pointerEvents = "";
3321 }, 100);
3322 }, { passive: true });
3323 }
3324 this.notifiedFocused = view.hasFocus;
3325 // On Safari adding an input event handler somehow prevents an
3326 // issue where the composition vanishes when you press enter.
3327 if (browser.safari)
3328 view.contentDOM.addEventListener("input", () => null);
3329 }
3330 setSelectionOrigin(origin) {
3331 this.lastSelectionOrigin = origin;
3332 this.lastSelectionTime = Date.now();
3333 }
3334 ensureHandlers(view, plugins) {
3335 var _a;
3336 let handlers;
3337 this.customHandlers = [];
3338 for (let plugin of plugins)
3339 if (handlers = (_a = plugin.update(view).spec) === null || _a === void 0 ? void 0 : _a.domEventHandlers) {
3340 this.customHandlers.push({ plugin: plugin.value, handlers });
3341 for (let type in handlers)
3342 if (this.registeredEvents.indexOf(type) < 0 && type != "scroll") {
3343 this.registeredEvents.push(type);
3344 view.contentDOM.addEventListener(type, (event) => {
3345 if (!eventBelongsToEditor(view, event))
3346 return;
3347 if (this.runCustomHandlers(type, view, event))
3348 event.preventDefault();
3349 });
3350 }
3351 }
3352 }
3353 runCustomHandlers(type, view, event) {
3354 for (let set of this.customHandlers) {
3355 let handler = set.handlers[type];
3356 if (handler) {
3357 try {
3358 if (handler.call(set.plugin, event, view) || event.defaultPrevented)
3359 return true;
3360 }
3361 catch (e) {
3362 logException(view.state, e);
3363 }
3364 }
3365 }
3366 return false;
3367 }
3368 runScrollHandlers(view, event) {
3369 for (let set of this.customHandlers) {
3370 let handler = set.handlers.scroll;
3371 if (handler) {
3372 try {
3373 handler.call(set.plugin, event, view);
3374 }
3375 catch (e) {
3376 logException(view.state, e);
3377 }
3378 }
3379 }
3380 }
3381 keydown(view, event) {
3382 // Must always run, even if a custom handler handled the event
3383 this.lastKeyCode = event.keyCode;
3384 this.lastKeyTime = Date.now();
3385 if (event.keyCode == 9 && Date.now() < this.lastEscPress + 2000)
3386 return true;
3387 // Chrome for Android usually doesn't fire proper key events, but
3388 // occasionally does, usually surrounded by a bunch of complicated
3389 // composition changes. When an enter or backspace key event is
3390 // seen, hold off on handling DOM events for a bit, and then
3391 // dispatch it.
3392 if (browser.android && browser.chrome && !event.synthetic &&
3393 (event.keyCode == 13 || event.keyCode == 8)) {
3394 view.observer.delayAndroidKey(event.key, event.keyCode);
3395 return true;
3396 }
3397 // Prevent the default behavior of Enter on iOS makes the
3398 // virtual keyboard get stuck in the wrong (lowercase)
3399 // state. So we let it go through, and then, in
3400 // applyDOMChange, notify key handlers of it and reset to
3401 // the state they produce.
3402 let pending;
3403 if (browser.ios && (pending = PendingKeys.find(key => key.keyCode == event.keyCode)) &&
3404 !(event.ctrlKey || event.altKey || event.metaKey) && !event.synthetic) {
3405 this.pendingIOSKey = pending;
3406 setTimeout(() => this.flushIOSKey(view), 250);
3407 return true;
3408 }
3409 return false;
3410 }
3411 flushIOSKey(view) {
3412 let key = this.pendingIOSKey;
3413 if (!key)
3414 return false;
3415 this.pendingIOSKey = undefined;
3416 return dispatchKey(view.contentDOM, key.key, key.keyCode);
3417 }
3418 ignoreDuringComposition(event) {
3419 if (!/^key/.test(event.type))
3420 return false;
3421 if (this.composing > 0)
3422 return true;
3423 // See https://www.stum.de/2016/06/24/handling-ime-events-in-javascript/.
3424 // On some input method editors (IMEs), the Enter key is used to
3425 // confirm character selection. On Safari, when Enter is pressed,
3426 // compositionend and keydown events are sometimes emitted in the
3427 // wrong order. The key event should still be ignored, even when
3428 // it happens after the compositionend event.
3429 if (browser.safari && Date.now() - this.compositionEndedAt < 100) {
3430 this.compositionEndedAt = 0;
3431 return true;
3432 }
3433 return false;
3434 }
3435 mustFlushObserver(event) {
3436 return (event.type == "keydown" && event.keyCode != 229) ||
3437 event.type == "compositionend" && !browser.ios;
3438 }
3439 startMouseSelection(mouseSelection) {
3440 if (this.mouseSelection)
3441 this.mouseSelection.destroy();
3442 this.mouseSelection = mouseSelection;
3443 }
3444 update(update) {
3445 if (this.mouseSelection)
3446 this.mouseSelection.update(update);
3447 if (update.transactions.length)
3448 this.lastKeyCode = this.lastSelectionTime = 0;
3449 }
3450 destroy() {
3451 if (this.mouseSelection)
3452 this.mouseSelection.destroy();
3453 }
3454}
3455const PendingKeys = [
3456 { key: "Backspace", keyCode: 8, inputType: "deleteContentBackward" },
3457 { key: "Enter", keyCode: 13, inputType: "insertParagraph" },
3458 { key: "Delete", keyCode: 46, inputType: "deleteContentForward" }
3459];
3460// Key codes for modifier keys
3461const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
3462class MouseSelection {
3463 constructor(view, startEvent, style, mustSelect) {
3464 this.view = view;
3465 this.style = style;
3466 this.mustSelect = mustSelect;
3467 this.lastEvent = startEvent;
3468 let doc = view.contentDOM.ownerDocument;
3469 doc.addEventListener("mousemove", this.move = this.move.bind(this));
3470 doc.addEventListener("mouseup", this.up = this.up.bind(this));
3471 this.extend = startEvent.shiftKey;
3472 this.multiple = view.state.facet(EditorState.allowMultipleSelections) && addsSelectionRange(view, startEvent);
3473 this.dragMove = dragMovesSelection(view, startEvent);
3474 this.dragging = isInPrimarySelection(view, startEvent) && getClickType(startEvent) == 1 ? null : false;
3475 // When clicking outside of the selection, immediately apply the
3476 // effect of starting the selection
3477 if (this.dragging === false) {
3478 startEvent.preventDefault();
3479 this.select(startEvent);
3480 }
3481 }
3482 move(event) {
3483 if (event.buttons == 0)
3484 return this.destroy();
3485 if (this.dragging !== false)
3486 return;
3487 this.select(this.lastEvent = event);
3488 }
3489 up(event) {
3490 if (this.dragging == null)
3491 this.select(this.lastEvent);
3492 if (!this.dragging)
3493 event.preventDefault();
3494 this.destroy();
3495 }
3496 destroy() {
3497 let doc = this.view.contentDOM.ownerDocument;
3498 doc.removeEventListener("mousemove", this.move);
3499 doc.removeEventListener("mouseup", this.up);
3500 this.view.inputState.mouseSelection = null;
3501 }
3502 select(event) {
3503 let selection = this.style.get(event, this.extend, this.multiple);
3504 if (this.mustSelect || !selection.eq(this.view.state.selection) ||
3505 selection.main.assoc != this.view.state.selection.main.assoc)
3506 this.view.dispatch({
3507 selection,
3508 userEvent: "select.pointer",
3509 scrollIntoView: true
3510 });
3511 this.mustSelect = false;
3512 }
3513 update(update) {
3514 if (update.docChanged && this.dragging)
3515 this.dragging = this.dragging.map(update.changes);
3516 if (this.style.update(update))
3517 setTimeout(() => this.select(this.lastEvent), 20);
3518 }
3519}
3520function addsSelectionRange(view, event) {
3521 let facet = view.state.facet(clickAddsSelectionRange);
3522 return facet.length ? facet[0](event) : browser.mac ? event.metaKey : event.ctrlKey;
3523}
3524function dragMovesSelection(view, event) {
3525 let facet = view.state.facet(dragMovesSelection$1);
3526 return facet.length ? facet[0](event) : browser.mac ? !event.altKey : !event.ctrlKey;
3527}
3528function isInPrimarySelection(view, event) {
3529 let { main } = view.state.selection;
3530 if (main.empty)
3531 return false;
3532 // On boundary clicks, check whether the coordinates are inside the
3533 // selection's client rectangles
3534 let sel = getSelection(view.root);
3535 if (!sel || sel.rangeCount == 0)
3536 return true;
3537 let rects = sel.getRangeAt(0).getClientRects();
3538 for (let i = 0; i < rects.length; i++) {
3539 let rect = rects[i];
3540 if (rect.left <= event.clientX && rect.right >= event.clientX &&
3541 rect.top <= event.clientY && rect.bottom >= event.clientY)
3542 return true;
3543 }
3544 return false;
3545}
3546function eventBelongsToEditor(view, event) {
3547 if (!event.bubbles)
3548 return true;
3549 if (event.defaultPrevented)
3550 return false;
3551 for (let node = event.target, cView; node != view.contentDOM; node = node.parentNode)
3552 if (!node || node.nodeType == 11 || ((cView = ContentView.get(node)) && cView.ignoreEvent(event)))
3553 return false;
3554 return true;
3555}
3556const handlers = /*@__PURE__*/Object.create(null);
3557// This is very crude, but unfortunately both these browsers _pretend_
3558// that they have a clipboard API—all the objects and methods are
3559// there, they just don't work, and they are hard to test.
3560const brokenClipboardAPI = (browser.ie && browser.ie_version < 15) ||
3561 (browser.ios && browser.webkit_version < 604);
3562function capturePaste(view) {
3563 let parent = view.dom.parentNode;
3564 if (!parent)
3565 return;
3566 let target = parent.appendChild(document.createElement("textarea"));
3567 target.style.cssText = "position: fixed; left: -10000px; top: 10px";
3568 target.focus();
3569 setTimeout(() => {
3570 view.focus();
3571 target.remove();
3572 doPaste(view, target.value);
3573 }, 50);
3574}
3575function doPaste(view, input) {
3576 let { state } = view, changes, i = 1, text = state.toText(input);
3577 let byLine = text.lines == state.selection.ranges.length;
3578 let linewise = lastLinewiseCopy != null && state.selection.ranges.every(r => r.empty) && lastLinewiseCopy == text.toString();
3579 if (linewise) {
3580 let lastLine = -1;
3581 changes = state.changeByRange(range => {
3582 let line = state.doc.lineAt(range.from);
3583 if (line.from == lastLine)
3584 return { range };
3585 lastLine = line.from;
3586 let insert = state.toText((byLine ? text.line(i++).text : input) + state.lineBreak);
3587 return { changes: { from: line.from, insert },
3588 range: EditorSelection.cursor(range.from + insert.length) };
3589 });
3590 }
3591 else if (byLine) {
3592 changes = state.changeByRange(range => {
3593 let line = text.line(i++);
3594 return { changes: { from: range.from, to: range.to, insert: line.text },
3595 range: EditorSelection.cursor(range.from + line.length) };
3596 });
3597 }
3598 else {
3599 changes = state.replaceSelection(text);
3600 }
3601 view.dispatch(changes, {
3602 userEvent: "input.paste",
3603 scrollIntoView: true
3604 });
3605}
3606handlers.keydown = (view, event) => {
3607 view.inputState.setSelectionOrigin("select");
3608 if (event.keyCode == 27)
3609 view.inputState.lastEscPress = Date.now();
3610 else if (modifierCodes.indexOf(event.keyCode) < 0)
3611 view.inputState.lastEscPress = 0;
3612};
3613let lastTouch = 0;
3614handlers.touchstart = (view, e) => {
3615 lastTouch = Date.now();
3616 view.inputState.setSelectionOrigin("select.pointer");
3617};
3618handlers.touchmove = view => {
3619 view.inputState.setSelectionOrigin("select.pointer");
3620};
3621handlers.mousedown = (view, event) => {
3622 view.observer.flush();
3623 if (lastTouch > Date.now() - 2000 && getClickType(event) == 1)
3624 return; // Ignore touch interaction
3625 let style = null;
3626 for (let makeStyle of view.state.facet(mouseSelectionStyle)) {
3627 style = makeStyle(view, event);
3628 if (style)
3629 break;
3630 }
3631 if (!style && event.button == 0)
3632 style = basicMouseSelection(view, event);
3633 if (style) {
3634 let mustFocus = view.root.activeElement != view.contentDOM;
3635 if (mustFocus)
3636 view.observer.ignore(() => focusPreventScroll(view.contentDOM));
3637 view.inputState.startMouseSelection(new MouseSelection(view, event, style, mustFocus));
3638 }
3639};
3640function rangeForClick(view, pos, bias, type) {
3641 if (type == 1) { // Single click
3642 return EditorSelection.cursor(pos, bias);
3643 }
3644 else if (type == 2) { // Double click
3645 return groupAt(view.state, pos, bias);
3646 }
3647 else { // Triple click
3648 let visual = LineView.find(view.docView, pos), line = view.state.doc.lineAt(visual ? visual.posAtEnd : pos);
3649 let from = visual ? visual.posAtStart : line.from, to = visual ? visual.posAtEnd : line.to;
3650 if (to < view.state.doc.length && to == line.to)
3651 to++;
3652 return EditorSelection.range(from, to);
3653 }
3654}
3655let insideY = (y, rect) => y >= rect.top && y <= rect.bottom;
3656let inside = (x, y, rect) => insideY(y, rect) && x >= rect.left && x <= rect.right;
3657// Try to determine, for the given coordinates, associated with the
3658// given position, whether they are related to the element before or
3659// the element after the position.
3660function findPositionSide(view, pos, x, y) {
3661 let line = LineView.find(view.docView, pos);
3662 if (!line)
3663 return 1;
3664 let off = pos - line.posAtStart;
3665 // Line boundaries point into the line
3666 if (off == 0)
3667 return 1;
3668 if (off == line.length)
3669 return -1;
3670 // Positions on top of an element point at that element
3671 let before = line.coordsAt(off, -1);
3672 if (before && inside(x, y, before))
3673 return -1;
3674 let after = line.coordsAt(off, 1);
3675 if (after && inside(x, y, after))
3676 return 1;
3677 // This is probably a line wrap point. Pick before if the point is
3678 // beside it.
3679 return before && insideY(y, before) ? -1 : 1;
3680}
3681function queryPos(view, event) {
3682 let pos = view.posAtCoords({ x: event.clientX, y: event.clientY }, false);
3683 return { pos, bias: findPositionSide(view, pos, event.clientX, event.clientY) };
3684}
3685const BadMouseDetail = browser.ie && browser.ie_version <= 11;
3686let lastMouseDown = null, lastMouseDownCount = 0, lastMouseDownTime = 0;
3687function getClickType(event) {
3688 if (!BadMouseDetail)
3689 return event.detail;
3690 let last = lastMouseDown, lastTime = lastMouseDownTime;
3691 lastMouseDown = event;
3692 lastMouseDownTime = Date.now();
3693 return lastMouseDownCount = !last || (lastTime > Date.now() - 400 && Math.abs(last.clientX - event.clientX) < 2 &&
3694 Math.abs(last.clientY - event.clientY) < 2) ? (lastMouseDownCount + 1) % 3 : 1;
3695}
3696function basicMouseSelection(view, event) {
3697 let start = queryPos(view, event), type = getClickType(event);
3698 let startSel = view.state.selection;
3699 let last = start, lastEvent = event;
3700 return {
3701 update(update) {
3702 if (update.docChanged) {
3703 if (start)
3704 start.pos = update.changes.mapPos(start.pos);
3705 startSel = startSel.map(update.changes);
3706 lastEvent = null;
3707 }
3708 },
3709 get(event, extend, multiple) {
3710 let cur;
3711 if (lastEvent && event.clientX == lastEvent.clientX && event.clientY == lastEvent.clientY)
3712 cur = last;
3713 else {
3714 cur = last = queryPos(view, event);
3715 lastEvent = event;
3716 }
3717 if (!cur || !start)
3718 return startSel;
3719 let range = rangeForClick(view, cur.pos, cur.bias, type);
3720 if (start.pos != cur.pos && !extend) {
3721 let startRange = rangeForClick(view, start.pos, start.bias, type);
3722 let from = Math.min(startRange.from, range.from), to = Math.max(startRange.to, range.to);
3723 range = from < range.from ? EditorSelection.range(from, to) : EditorSelection.range(to, from);
3724 }
3725 if (extend)
3726 return startSel.replaceRange(startSel.main.extend(range.from, range.to));
3727 else if (multiple)
3728 return startSel.addRange(range);
3729 else
3730 return EditorSelection.create([range]);
3731 }
3732 };
3733}
3734handlers.dragstart = (view, event) => {
3735 let { selection: { main } } = view.state;
3736 let { mouseSelection } = view.inputState;
3737 if (mouseSelection)
3738 mouseSelection.dragging = main;
3739 if (event.dataTransfer) {
3740 event.dataTransfer.setData("Text", view.state.sliceDoc(main.from, main.to));
3741 event.dataTransfer.effectAllowed = "copyMove";
3742 }
3743};
3744function dropText(view, event, text, direct) {
3745 if (!text)
3746 return;
3747 let dropPos = view.posAtCoords({ x: event.clientX, y: event.clientY }, false);
3748 event.preventDefault();
3749 let { mouseSelection } = view.inputState;
3750 let del = direct && mouseSelection && mouseSelection.dragging && mouseSelection.dragMove ?
3751 { from: mouseSelection.dragging.from, to: mouseSelection.dragging.to } : null;
3752 let ins = { from: dropPos, insert: text };
3753 let changes = view.state.changes(del ? [del, ins] : ins);
3754 view.focus();
3755 view.dispatch({
3756 changes,
3757 selection: { anchor: changes.mapPos(dropPos, -1), head: changes.mapPos(dropPos, 1) },
3758 userEvent: del ? "move.drop" : "input.drop"
3759 });
3760}
3761handlers.drop = (view, event) => {
3762 if (!event.dataTransfer)
3763 return;
3764 if (view.state.readOnly)
3765 return event.preventDefault();
3766 let files = event.dataTransfer.files;
3767 if (files && files.length) { // For a file drop, read the file's text.
3768 event.preventDefault();
3769 let text = Array(files.length), read = 0;
3770 let finishFile = () => {
3771 if (++read == files.length)
3772 dropText(view, event, text.filter(s => s != null).join(view.state.lineBreak), false);
3773 };
3774 for (let i = 0; i < files.length; i++) {
3775 let reader = new FileReader;
3776 reader.onerror = finishFile;
3777 reader.onload = () => {
3778 if (!/[\x00-\x08\x0e-\x1f]{2}/.test(reader.result))
3779 text[i] = reader.result;
3780 finishFile();
3781 };
3782 reader.readAsText(files[i]);
3783 }
3784 }
3785 else {
3786 dropText(view, event, event.dataTransfer.getData("Text"), true);
3787 }
3788};
3789handlers.paste = (view, event) => {
3790 if (view.state.readOnly)
3791 return event.preventDefault();
3792 view.observer.flush();
3793 let data = brokenClipboardAPI ? null : event.clipboardData;
3794 if (data) {
3795 doPaste(view, data.getData("text/plain"));
3796 event.preventDefault();
3797 }
3798 else {
3799 capturePaste(view);
3800 }
3801};
3802function captureCopy(view, text) {
3803 // The extra wrapper is somehow necessary on IE/Edge to prevent the
3804 // content from being mangled when it is put onto the clipboard
3805 let parent = view.dom.parentNode;
3806 if (!parent)
3807 return;
3808 let target = parent.appendChild(document.createElement("textarea"));
3809 target.style.cssText = "position: fixed; left: -10000px; top: 10px";
3810 target.value = text;
3811 target.focus();
3812 target.selectionEnd = text.length;
3813 target.selectionStart = 0;
3814 setTimeout(() => {
3815 target.remove();
3816 view.focus();
3817 }, 50);
3818}
3819function copiedRange(state) {
3820 let content = [], ranges = [], linewise = false;
3821 for (let range of state.selection.ranges)
3822 if (!range.empty) {
3823 content.push(state.sliceDoc(range.from, range.to));
3824 ranges.push(range);
3825 }
3826 if (!content.length) {
3827 // Nothing selected, do a line-wise copy
3828 let upto = -1;
3829 for (let { from } of state.selection.ranges) {
3830 let line = state.doc.lineAt(from);
3831 if (line.number > upto) {
3832 content.push(line.text);
3833 ranges.push({ from: line.from, to: Math.min(state.doc.length, line.to + 1) });
3834 }
3835 upto = line.number;
3836 }
3837 linewise = true;
3838 }
3839 return { text: content.join(state.lineBreak), ranges, linewise };
3840}
3841let lastLinewiseCopy = null;
3842handlers.copy = handlers.cut = (view, event) => {
3843 let { text, ranges, linewise } = copiedRange(view.state);
3844 if (!text && !linewise)
3845 return;
3846 lastLinewiseCopy = linewise ? text : null;
3847 let data = brokenClipboardAPI ? null : event.clipboardData;
3848 if (data) {
3849 event.preventDefault();
3850 data.clearData();
3851 data.setData("text/plain", text);
3852 }
3853 else {
3854 captureCopy(view, text);
3855 }
3856 if (event.type == "cut" && !view.state.readOnly)
3857 view.dispatch({
3858 changes: ranges,
3859 scrollIntoView: true,
3860 userEvent: "delete.cut"
3861 });
3862};
3863function updateForFocusChange(view) {
3864 setTimeout(() => {
3865 if (view.hasFocus != view.inputState.notifiedFocused)
3866 view.update([]);
3867 }, 10);
3868}
3869handlers.focus = updateForFocusChange;
3870handlers.blur = view => {
3871 view.observer.clearSelectionRange();
3872 updateForFocusChange(view);
3873};
3874function forceClearComposition(view, rapid) {
3875 if (view.docView.compositionDeco.size) {
3876 view.inputState.rapidCompositionStart = rapid;
3877 try {
3878 view.update([]);
3879 }
3880 finally {
3881 view.inputState.rapidCompositionStart = false;
3882 }
3883 }
3884}
3885handlers.compositionstart = handlers.compositionupdate = view => {
3886 if (view.inputState.compositionFirstChange == null)
3887 view.inputState.compositionFirstChange = true;
3888 if (view.inputState.composing < 0) {
3889 // FIXME possibly set a timeout to clear it again on Android
3890 view.inputState.composing = 0;
3891 if (view.docView.compositionDeco.size) {
3892 view.observer.flush();
3893 forceClearComposition(view, true);
3894 }
3895 }
3896};
3897handlers.compositionend = view => {
3898 view.inputState.composing = -1;
3899 view.inputState.compositionEndedAt = Date.now();
3900 view.inputState.compositionFirstChange = null;
3901 setTimeout(() => {
3902 if (view.inputState.composing < 0)
3903 forceClearComposition(view, false);
3904 }, 50);
3905};
3906handlers.contextmenu = view => {
3907 view.inputState.lastContextMenu = Date.now();
3908};
3909handlers.beforeinput = (view, event) => {
3910 var _a;
3911 // Because Chrome Android doesn't fire useful key events, use
3912 // beforeinput to detect backspace (and possibly enter and delete,
3913 // but those usually don't even seem to fire beforeinput events at
3914 // the moment) and fake a key event for it.
3915 //
3916 // (preventDefault on beforeinput, though supported in the spec,
3917 // seems to do nothing at all on Chrome).
3918 let pending;
3919 if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
3920 view.observer.delayAndroidKey(pending.key, pending.keyCode);
3921 if (pending.key == "Backspace" || pending.key == "Delete") {
3922 let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
3923 setTimeout(() => {
3924 var _a;
3925 // Backspacing near uneditable nodes on Chrome Android sometimes
3926 // closes the virtual keyboard. This tries to crudely detect
3927 // that and refocus to get it back.
3928 if ((((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0) > startViewHeight + 10 && view.hasFocus) {
3929 view.contentDOM.blur();
3930 view.focus();
3931 }
3932 }, 100);
3933 }
3934 }
3935};
3936
3937const wrappingWhiteSpace = ["pre-wrap", "normal", "pre-line", "break-spaces"];
3938class HeightOracle {
3939 constructor() {
3940 this.doc = Text.empty;
3941 this.lineWrapping = false;
3942 this.heightSamples = {};
3943 this.lineHeight = 14;
3944 this.charWidth = 7;
3945 this.lineLength = 30;
3946 // Used to track, during updateHeight, if any actual heights changed
3947 this.heightChanged = false;
3948 }
3949 heightForGap(from, to) {
3950 let lines = this.doc.lineAt(to).number - this.doc.lineAt(from).number + 1;
3951 if (this.lineWrapping)
3952 lines += Math.ceil(((to - from) - (lines * this.lineLength * 0.5)) / this.lineLength);
3953 return this.lineHeight * lines;
3954 }
3955 heightForLine(length) {
3956 if (!this.lineWrapping)
3957 return this.lineHeight;
3958 let lines = 1 + Math.max(0, Math.ceil((length - this.lineLength) / (this.lineLength - 5)));
3959 return lines * this.lineHeight;
3960 }
3961 setDoc(doc) { this.doc = doc; return this; }
3962 mustRefreshForWrapping(whiteSpace) {
3963 return (wrappingWhiteSpace.indexOf(whiteSpace) > -1) != this.lineWrapping;
3964 }
3965 mustRefreshForHeights(lineHeights) {
3966 let newHeight = false;
3967 for (let i = 0; i < lineHeights.length; i++) {
3968 let h = lineHeights[i];
3969 if (h < 0) {
3970 i++;
3971 }
3972 else if (!this.heightSamples[Math.floor(h * 10)]) { // Round to .1 pixels
3973 newHeight = true;
3974 this.heightSamples[Math.floor(h * 10)] = true;
3975 }
3976 }
3977 return newHeight;
3978 }
3979 refresh(whiteSpace, lineHeight, charWidth, lineLength, knownHeights) {
3980 let lineWrapping = wrappingWhiteSpace.indexOf(whiteSpace) > -1;
3981 let changed = Math.round(lineHeight) != Math.round(this.lineHeight) || this.lineWrapping != lineWrapping;
3982 this.lineWrapping = lineWrapping;
3983 this.lineHeight = lineHeight;
3984 this.charWidth = charWidth;
3985 this.lineLength = lineLength;
3986 if (changed) {
3987 this.heightSamples = {};
3988 for (let i = 0; i < knownHeights.length; i++) {
3989 let h = knownHeights[i];
3990 if (h < 0)
3991 i++;
3992 else
3993 this.heightSamples[Math.floor(h * 10)] = true;
3994 }
3995 }
3996 return changed;
3997 }
3998}
3999// This object is used by `updateHeight` to make DOM measurements
4000// arrive at the right nides. The `heights` array is a sequence of
4001// block heights, starting from position `from`.
4002class MeasuredHeights {
4003 constructor(from, heights) {
4004 this.from = from;
4005 this.heights = heights;
4006 this.index = 0;
4007 }
4008 get more() { return this.index < this.heights.length; }
4009}
4010/**
4011Record used to represent information about a block-level element
4012in the editor view.
4013*/
4014class BlockInfo {
4015 /**
4016 @internal
4017 */
4018 constructor(
4019 /**
4020 The start of the element in the document.
4021 */
4022 from,
4023 /**
4024 The length of the element.
4025 */
4026 length,
4027 /**
4028 The top position of the element (relative to the top of the
4029 document).
4030 */
4031 top,
4032 /**
4033 Its height.
4034 */
4035 height,
4036 /**
4037 The type of element this is. When querying lines, this may be
4038 an array of all the blocks that make up the line.
4039 */
4040 type) {
4041 this.from = from;
4042 this.length = length;
4043 this.top = top;
4044 this.height = height;
4045 this.type = type;
4046 }
4047 /**
4048 The end of the element as a document position.
4049 */
4050 get to() { return this.from + this.length; }
4051 /**
4052 The bottom position of the element.
4053 */
4054 get bottom() { return this.top + this.height; }
4055 /**
4056 @internal
4057 */
4058 join(other) {
4059 let detail = (Array.isArray(this.type) ? this.type : [this])
4060 .concat(Array.isArray(other.type) ? other.type : [other]);
4061 return new BlockInfo(this.from, this.length + other.length, this.top, this.height + other.height, detail);
4062 }
4063}
4064var QueryType = /*@__PURE__*/(function (QueryType) {
4065 QueryType[QueryType["ByPos"] = 0] = "ByPos";
4066 QueryType[QueryType["ByHeight"] = 1] = "ByHeight";
4067 QueryType[QueryType["ByPosNoHeight"] = 2] = "ByPosNoHeight";
4068return QueryType})(QueryType || (QueryType = {}));
4069const Epsilon = 1e-3;
4070class HeightMap {
4071 constructor(length, // The number of characters covered
4072 height, // Height of this part of the document
4073 flags = 2 /* Outdated */) {
4074 this.length = length;
4075 this.height = height;
4076 this.flags = flags;
4077 }
4078 get outdated() { return (this.flags & 2 /* Outdated */) > 0; }
4079 set outdated(value) { this.flags = (value ? 2 /* Outdated */ : 0) | (this.flags & ~2 /* Outdated */); }
4080 setHeight(oracle, height) {
4081 if (this.height != height) {
4082 if (Math.abs(this.height - height) > Epsilon)
4083 oracle.heightChanged = true;
4084 this.height = height;
4085 }
4086 }
4087 // Base case is to replace a leaf node, which simply builds a tree
4088 // from the new nodes and returns that (HeightMapBranch and
4089 // HeightMapGap override this to actually use from/to)
4090 replace(_from, _to, nodes) {
4091 return HeightMap.of(nodes);
4092 }
4093 // Again, these are base cases, and are overridden for branch and gap nodes.
4094 decomposeLeft(_to, result) { result.push(this); }
4095 decomposeRight(_from, result) { result.push(this); }
4096 applyChanges(decorations, oldDoc, oracle, changes) {
4097 let me = this;
4098 for (let i = changes.length - 1; i >= 0; i--) {
4099 let { fromA, toA, fromB, toB } = changes[i];
4100 let start = me.lineAt(fromA, QueryType.ByPosNoHeight, oldDoc, 0, 0);
4101 let end = start.to >= toA ? start : me.lineAt(toA, QueryType.ByPosNoHeight, oldDoc, 0, 0);
4102 toB += end.to - toA;
4103 toA = end.to;
4104 while (i > 0 && start.from <= changes[i - 1].toA) {
4105 fromA = changes[i - 1].fromA;
4106 fromB = changes[i - 1].fromB;
4107 i--;
4108 if (fromA < start.from)
4109 start = me.lineAt(fromA, QueryType.ByPosNoHeight, oldDoc, 0, 0);
4110 }
4111 fromB += start.from - fromA;
4112 fromA = start.from;
4113 let nodes = NodeBuilder.build(oracle, decorations, fromB, toB);
4114 me = me.replace(fromA, toA, nodes);
4115 }
4116 return me.updateHeight(oracle, 0);
4117 }
4118 static empty() { return new HeightMapText(0, 0); }
4119 // nodes uses null values to indicate the position of line breaks.
4120 // There are never line breaks at the start or end of the array, or
4121 // two line breaks next to each other, and the array isn't allowed
4122 // to be empty (same restrictions as return value from the builder).
4123 static of(nodes) {
4124 if (nodes.length == 1)
4125 return nodes[0];
4126 let i = 0, j = nodes.length, before = 0, after = 0;
4127 for (;;) {
4128 if (i == j) {
4129 if (before > after * 2) {
4130 let split = nodes[i - 1];
4131 if (split.break)
4132 nodes.splice(--i, 1, split.left, null, split.right);
4133 else
4134 nodes.splice(--i, 1, split.left, split.right);
4135 j += 1 + split.break;
4136 before -= split.size;
4137 }
4138 else if (after > before * 2) {
4139 let split = nodes[j];
4140 if (split.break)
4141 nodes.splice(j, 1, split.left, null, split.right);
4142 else
4143 nodes.splice(j, 1, split.left, split.right);
4144 j += 2 + split.break;
4145 after -= split.size;
4146 }
4147 else {
4148 break;
4149 }
4150 }
4151 else if (before < after) {
4152 let next = nodes[i++];
4153 if (next)
4154 before += next.size;
4155 }
4156 else {
4157 let next = nodes[--j];
4158 if (next)
4159 after += next.size;
4160 }
4161 }
4162 let brk = 0;
4163 if (nodes[i - 1] == null) {
4164 brk = 1;
4165 i--;
4166 }
4167 else if (nodes[i] == null) {
4168 brk = 1;
4169 j++;
4170 }
4171 return new HeightMapBranch(HeightMap.of(nodes.slice(0, i)), brk, HeightMap.of(nodes.slice(j)));
4172 }
4173}
4174HeightMap.prototype.size = 1;
4175class HeightMapBlock extends HeightMap {
4176 constructor(length, height, type) {
4177 super(length, height);
4178 this.type = type;
4179 }
4180 blockAt(_height, _doc, top, offset) {
4181 return new BlockInfo(offset, this.length, top, this.height, this.type);
4182 }
4183 lineAt(_value, _type, doc, top, offset) {
4184 return this.blockAt(0, doc, top, offset);
4185 }
4186 forEachLine(from, to, doc, top, offset, f) {
4187 if (from <= offset + this.length && to >= offset)
4188 f(this.blockAt(0, doc, top, offset));
4189 }
4190 updateHeight(oracle, offset = 0, _force = false, measured) {
4191 if (measured && measured.from <= offset && measured.more)
4192 this.setHeight(oracle, measured.heights[measured.index++]);
4193 this.outdated = false;
4194 return this;
4195 }
4196 toString() { return `block(${this.length})`; }
4197}
4198class HeightMapText extends HeightMapBlock {
4199 constructor(length, height) {
4200 super(length, height, BlockType.Text);
4201 this.collapsed = 0; // Amount of collapsed content in the line
4202 this.widgetHeight = 0; // Maximum inline widget height
4203 }
4204 replace(_from, _to, nodes) {
4205 let node = nodes[0];
4206 if (nodes.length == 1 && (node instanceof HeightMapText || node instanceof HeightMapGap && (node.flags & 4 /* SingleLine */)) &&
4207 Math.abs(this.length - node.length) < 10) {
4208 if (node instanceof HeightMapGap)
4209 node = new HeightMapText(node.length, this.height);
4210 else
4211 node.height = this.height;
4212 if (!this.outdated)
4213 node.outdated = false;
4214 return node;
4215 }
4216 else {
4217 return HeightMap.of(nodes);
4218 }
4219 }
4220 updateHeight(oracle, offset = 0, force = false, measured) {
4221 if (measured && measured.from <= offset && measured.more)
4222 this.setHeight(oracle, measured.heights[measured.index++]);
4223 else if (force || this.outdated)
4224 this.setHeight(oracle, Math.max(this.widgetHeight, oracle.heightForLine(this.length - this.collapsed)));
4225 this.outdated = false;
4226 return this;
4227 }
4228 toString() {
4229 return `line(${this.length}${this.collapsed ? -this.collapsed : ""}${this.widgetHeight ? ":" + this.widgetHeight : ""})`;
4230 }
4231}
4232class HeightMapGap extends HeightMap {
4233 constructor(length) { super(length, 0); }
4234 lines(doc, offset) {
4235 let firstLine = doc.lineAt(offset).number, lastLine = doc.lineAt(offset + this.length).number;
4236 return { firstLine, lastLine, lineHeight: this.height / (lastLine - firstLine + 1) };
4237 }
4238 blockAt(height, doc, top, offset) {
4239 let { firstLine, lastLine, lineHeight } = this.lines(doc, offset);
4240 let line = Math.max(0, Math.min(lastLine - firstLine, Math.floor((height - top) / lineHeight)));
4241 let { from, length } = doc.line(firstLine + line);
4242 return new BlockInfo(from, length, top + lineHeight * line, lineHeight, BlockType.Text);
4243 }
4244 lineAt(value, type, doc, top, offset) {
4245 if (type == QueryType.ByHeight)
4246 return this.blockAt(value, doc, top, offset);
4247 if (type == QueryType.ByPosNoHeight) {
4248 let { from, to } = doc.lineAt(value);
4249 return new BlockInfo(from, to - from, 0, 0, BlockType.Text);
4250 }
4251 let { firstLine, lineHeight } = this.lines(doc, offset);
4252 let { from, length, number } = doc.lineAt(value);
4253 return new BlockInfo(from, length, top + lineHeight * (number - firstLine), lineHeight, BlockType.Text);
4254 }
4255 forEachLine(from, to, doc, top, offset, f) {
4256 let { firstLine, lineHeight } = this.lines(doc, offset);
4257 for (let pos = Math.max(from, offset), end = Math.min(offset + this.length, to); pos <= end;) {
4258 let line = doc.lineAt(pos);
4259 if (pos == from)
4260 top += lineHeight * (line.number - firstLine);
4261 f(new BlockInfo(line.from, line.length, top, lineHeight, BlockType.Text));
4262 top += lineHeight;
4263 pos = line.to + 1;
4264 }
4265 }
4266 replace(from, to, nodes) {
4267 let after = this.length - to;
4268 if (after > 0) {
4269 let last = nodes[nodes.length - 1];
4270 if (last instanceof HeightMapGap)
4271 nodes[nodes.length - 1] = new HeightMapGap(last.length + after);
4272 else
4273 nodes.push(null, new HeightMapGap(after - 1));
4274 }
4275 if (from > 0) {
4276 let first = nodes[0];
4277 if (first instanceof HeightMapGap)
4278 nodes[0] = new HeightMapGap(from + first.length);
4279 else
4280 nodes.unshift(new HeightMapGap(from - 1), null);
4281 }
4282 return HeightMap.of(nodes);
4283 }
4284 decomposeLeft(to, result) {
4285 result.push(new HeightMapGap(to - 1), null);
4286 }
4287 decomposeRight(from, result) {
4288 result.push(null, new HeightMapGap(this.length - from - 1));
4289 }
4290 updateHeight(oracle, offset = 0, force = false, measured) {
4291 let end = offset + this.length;
4292 if (measured && measured.from <= offset + this.length && measured.more) {
4293 // Fill in part of this gap with measured lines. We know there
4294 // can't be widgets or collapsed ranges in those lines, because
4295 // they would already have been added to the heightmap (gaps
4296 // only contain plain text).
4297 let nodes = [], pos = Math.max(offset, measured.from), singleHeight = -1;
4298 let wasChanged = oracle.heightChanged;
4299 if (measured.from > offset)
4300 nodes.push(new HeightMapGap(measured.from - offset - 1).updateHeight(oracle, offset));
4301 while (pos <= end && measured.more) {
4302 let len = oracle.doc.lineAt(pos).length;
4303 if (nodes.length)
4304 nodes.push(null);
4305 let height = measured.heights[measured.index++];
4306 if (singleHeight == -1)
4307 singleHeight = height;
4308 else if (Math.abs(height - singleHeight) >= Epsilon)
4309 singleHeight = -2;
4310 let line = new HeightMapText(len, height);
4311 line.outdated = false;
4312 nodes.push(line);
4313 pos += len + 1;
4314 }
4315 if (pos <= end)
4316 nodes.push(null, new HeightMapGap(end - pos).updateHeight(oracle, pos));
4317 let result = HeightMap.of(nodes);
4318 oracle.heightChanged = wasChanged || singleHeight < 0 || Math.abs(result.height - this.height) >= Epsilon ||
4319 Math.abs(singleHeight - this.lines(oracle.doc, offset).lineHeight) >= Epsilon;
4320 return result;
4321 }
4322 else if (force || this.outdated) {
4323 this.setHeight(oracle, oracle.heightForGap(offset, offset + this.length));
4324 this.outdated = false;
4325 }
4326 return this;
4327 }
4328 toString() { return `gap(${this.length})`; }
4329}
4330class HeightMapBranch extends HeightMap {
4331 constructor(left, brk, right) {
4332 super(left.length + brk + right.length, left.height + right.height, brk | (left.outdated || right.outdated ? 2 /* Outdated */ : 0));
4333 this.left = left;
4334 this.right = right;
4335 this.size = left.size + right.size;
4336 }
4337 get break() { return this.flags & 1 /* Break */; }
4338 blockAt(height, doc, top, offset) {
4339 let mid = top + this.left.height;
4340 return height < mid ? this.left.blockAt(height, doc, top, offset)
4341 : this.right.blockAt(height, doc, mid, offset + this.left.length + this.break);
4342 }
4343 lineAt(value, type, doc, top, offset) {
4344 let rightTop = top + this.left.height, rightOffset = offset + this.left.length + this.break;
4345 let left = type == QueryType.ByHeight ? value < rightTop : value < rightOffset;
4346 let base = left ? this.left.lineAt(value, type, doc, top, offset)
4347 : this.right.lineAt(value, type, doc, rightTop, rightOffset);
4348 if (this.break || (left ? base.to < rightOffset : base.from > rightOffset))
4349 return base;
4350 let subQuery = type == QueryType.ByPosNoHeight ? QueryType.ByPosNoHeight : QueryType.ByPos;
4351 if (left)
4352 return base.join(this.right.lineAt(rightOffset, subQuery, doc, rightTop, rightOffset));
4353 else
4354 return this.left.lineAt(rightOffset, subQuery, doc, top, offset).join(base);
4355 }
4356 forEachLine(from, to, doc, top, offset, f) {
4357 let rightTop = top + this.left.height, rightOffset = offset + this.left.length + this.break;
4358 if (this.break) {
4359 if (from < rightOffset)
4360 this.left.forEachLine(from, to, doc, top, offset, f);
4361 if (to >= rightOffset)
4362 this.right.forEachLine(from, to, doc, rightTop, rightOffset, f);
4363 }
4364 else {
4365 let mid = this.lineAt(rightOffset, QueryType.ByPos, doc, top, offset);
4366 if (from < mid.from)
4367 this.left.forEachLine(from, mid.from - 1, doc, top, offset, f);
4368 if (mid.to >= from && mid.from <= to)
4369 f(mid);
4370 if (to > mid.to)
4371 this.right.forEachLine(mid.to + 1, to, doc, rightTop, rightOffset, f);
4372 }
4373 }
4374 replace(from, to, nodes) {
4375 let rightStart = this.left.length + this.break;
4376 if (to < rightStart)
4377 return this.balanced(this.left.replace(from, to, nodes), this.right);
4378 if (from > this.left.length)
4379 return this.balanced(this.left, this.right.replace(from - rightStart, to - rightStart, nodes));
4380 let result = [];
4381 if (from > 0)
4382 this.decomposeLeft(from, result);
4383 let left = result.length;
4384 for (let node of nodes)
4385 result.push(node);
4386 if (from > 0)
4387 mergeGaps(result, left - 1);
4388 if (to < this.length) {
4389 let right = result.length;
4390 this.decomposeRight(to, result);
4391 mergeGaps(result, right);
4392 }
4393 return HeightMap.of(result);
4394 }
4395 decomposeLeft(to, result) {
4396 let left = this.left.length;
4397 if (to <= left)
4398 return this.left.decomposeLeft(to, result);
4399 result.push(this.left);
4400 if (this.break) {
4401 left++;
4402 if (to >= left)
4403 result.push(null);
4404 }
4405 if (to > left)
4406 this.right.decomposeLeft(to - left, result);
4407 }
4408 decomposeRight(from, result) {
4409 let left = this.left.length, right = left + this.break;
4410 if (from >= right)
4411 return this.right.decomposeRight(from - right, result);
4412 if (from < left)
4413 this.left.decomposeRight(from, result);
4414 if (this.break && from < right)
4415 result.push(null);
4416 result.push(this.right);
4417 }
4418 balanced(left, right) {
4419 if (left.size > 2 * right.size || right.size > 2 * left.size)
4420 return HeightMap.of(this.break ? [left, null, right] : [left, right]);
4421 this.left = left;
4422 this.right = right;
4423 this.height = left.height + right.height;
4424 this.outdated = left.outdated || right.outdated;
4425 this.size = left.size + right.size;
4426 this.length = left.length + this.break + right.length;
4427 return this;
4428 }
4429 updateHeight(oracle, offset = 0, force = false, measured) {
4430 let { left, right } = this, rightStart = offset + left.length + this.break, rebalance = null;
4431 if (measured && measured.from <= offset + left.length && measured.more)
4432 rebalance = left = left.updateHeight(oracle, offset, force, measured);
4433 else
4434 left.updateHeight(oracle, offset, force);
4435 if (measured && measured.from <= rightStart + right.length && measured.more)
4436 rebalance = right = right.updateHeight(oracle, rightStart, force, measured);
4437 else
4438 right.updateHeight(oracle, rightStart, force);
4439 if (rebalance)
4440 return this.balanced(left, right);
4441 this.height = this.left.height + this.right.height;
4442 this.outdated = false;
4443 return this;
4444 }
4445 toString() { return this.left + (this.break ? " " : "-") + this.right; }
4446}
4447function mergeGaps(nodes, around) {
4448 let before, after;
4449 if (nodes[around] == null &&
4450 (before = nodes[around - 1]) instanceof HeightMapGap &&
4451 (after = nodes[around + 1]) instanceof HeightMapGap)
4452 nodes.splice(around - 1, 3, new HeightMapGap(before.length + 1 + after.length));
4453}
4454const relevantWidgetHeight = 5;
4455class NodeBuilder {
4456 constructor(pos, oracle) {
4457 this.pos = pos;
4458 this.oracle = oracle;
4459 this.nodes = [];
4460 this.lineStart = -1;
4461 this.lineEnd = -1;
4462 this.covering = null;
4463 this.writtenTo = pos;
4464 }
4465 get isCovered() {
4466 return this.covering && this.nodes[this.nodes.length - 1] == this.covering;
4467 }
4468 span(_from, to) {
4469 if (this.lineStart > -1) {
4470 let end = Math.min(to, this.lineEnd), last = this.nodes[this.nodes.length - 1];
4471 if (last instanceof HeightMapText)
4472 last.length += end - this.pos;
4473 else if (end > this.pos || !this.isCovered)
4474 this.nodes.push(new HeightMapText(end - this.pos, -1));
4475 this.writtenTo = end;
4476 if (to > end) {
4477 this.nodes.push(null);
4478 this.writtenTo++;
4479 this.lineStart = -1;
4480 }
4481 }
4482 this.pos = to;
4483 }
4484 point(from, to, deco) {
4485 if (from < to || deco.heightRelevant) {
4486 let height = deco.widget ? deco.widget.estimatedHeight : 0;
4487 if (height < 0)
4488 height = this.oracle.lineHeight;
4489 let len = to - from;
4490 if (deco.block) {
4491 this.addBlock(new HeightMapBlock(len, height, deco.type));
4492 }
4493 else if (len || height >= relevantWidgetHeight) {
4494 this.addLineDeco(height, len);
4495 }
4496 }
4497 else if (to > from) {
4498 this.span(from, to);
4499 }
4500 if (this.lineEnd > -1 && this.lineEnd < this.pos)
4501 this.lineEnd = this.oracle.doc.lineAt(this.pos).to;
4502 }
4503 enterLine() {
4504 if (this.lineStart > -1)
4505 return;
4506 let { from, to } = this.oracle.doc.lineAt(this.pos);
4507 this.lineStart = from;
4508 this.lineEnd = to;
4509 if (this.writtenTo < from) {
4510 if (this.writtenTo < from - 1 || this.nodes[this.nodes.length - 1] == null)
4511 this.nodes.push(this.blankContent(this.writtenTo, from - 1));
4512 this.nodes.push(null);
4513 }
4514 if (this.pos > from)
4515 this.nodes.push(new HeightMapText(this.pos - from, -1));
4516 this.writtenTo = this.pos;
4517 }
4518 blankContent(from, to) {
4519 let gap = new HeightMapGap(to - from);
4520 if (this.oracle.doc.lineAt(from).to == to)
4521 gap.flags |= 4 /* SingleLine */;
4522 return gap;
4523 }
4524 ensureLine() {
4525 this.enterLine();
4526 let last = this.nodes.length ? this.nodes[this.nodes.length - 1] : null;
4527 if (last instanceof HeightMapText)
4528 return last;
4529 let line = new HeightMapText(0, -1);
4530 this.nodes.push(line);
4531 return line;
4532 }
4533 addBlock(block) {
4534 this.enterLine();
4535 if (block.type == BlockType.WidgetAfter && !this.isCovered)
4536 this.ensureLine();
4537 this.nodes.push(block);
4538 this.writtenTo = this.pos = this.pos + block.length;
4539 if (block.type != BlockType.WidgetBefore)
4540 this.covering = block;
4541 }
4542 addLineDeco(height, length) {
4543 let line = this.ensureLine();
4544 line.length += length;
4545 line.collapsed += length;
4546 line.widgetHeight = Math.max(line.widgetHeight, height);
4547 this.writtenTo = this.pos = this.pos + length;
4548 }
4549 finish(from) {
4550 let last = this.nodes.length == 0 ? null : this.nodes[this.nodes.length - 1];
4551 if (this.lineStart > -1 && !(last instanceof HeightMapText) && !this.isCovered)
4552 this.nodes.push(new HeightMapText(0, -1));
4553 else if (this.writtenTo < this.pos || last == null)
4554 this.nodes.push(this.blankContent(this.writtenTo, this.pos));
4555 let pos = from;
4556 for (let node of this.nodes) {
4557 if (node instanceof HeightMapText)
4558 node.updateHeight(this.oracle, pos);
4559 pos += node ? node.length : 1;
4560 }
4561 return this.nodes;
4562 }
4563 // Always called with a region that on both sides either stretches
4564 // to a line break or the end of the document.
4565 // The returned array uses null to indicate line breaks, but never
4566 // starts or ends in a line break, or has multiple line breaks next
4567 // to each other.
4568 static build(oracle, decorations, from, to) {
4569 let builder = new NodeBuilder(from, oracle);
4570 RangeSet.spans(decorations, from, to, builder, 0);
4571 return builder.finish(from);
4572 }
4573}
4574function heightRelevantDecoChanges(a, b, diff) {
4575 let comp = new DecorationComparator;
4576 RangeSet.compare(a, b, diff, comp, 0);
4577 return comp.changes;
4578}
4579class DecorationComparator {
4580 constructor() {
4581 this.changes = [];
4582 }
4583 compareRange() { }
4584 comparePoint(from, to, a, b) {
4585 if (from < to || a && a.heightRelevant || b && b.heightRelevant)
4586 addRange(from, to, this.changes, 5);
4587 }
4588}
4589
4590function visiblePixelRange(dom, paddingTop) {
4591 let rect = dom.getBoundingClientRect();
4592 let left = Math.max(0, rect.left), right = Math.min(innerWidth, rect.right);
4593 let top = Math.max(0, rect.top), bottom = Math.min(innerHeight, rect.bottom);
4594 let body = dom.ownerDocument.body;
4595 for (let parent = dom.parentNode; parent && parent != body;) {
4596 if (parent.nodeType == 1) {
4597 let elt = parent;
4598 let style = window.getComputedStyle(elt);
4599 if ((elt.scrollHeight > elt.clientHeight || elt.scrollWidth > elt.clientWidth) &&
4600 style.overflow != "visible") {
4601 let parentRect = elt.getBoundingClientRect();
4602 left = Math.max(left, parentRect.left);
4603 right = Math.min(right, parentRect.right);
4604 top = Math.max(top, parentRect.top);
4605 bottom = Math.min(bottom, parentRect.bottom);
4606 }
4607 parent = style.position == "absolute" || style.position == "fixed" ? elt.offsetParent : elt.parentNode;
4608 }
4609 else if (parent.nodeType == 11) { // Shadow root
4610 parent = parent.host;
4611 }
4612 else {
4613 break;
4614 }
4615 }
4616 return { left: left - rect.left, right: Math.max(left, right) - rect.left,
4617 top: top - (rect.top + paddingTop), bottom: Math.max(top, bottom) - (rect.top + paddingTop) };
4618}
4619function fullPixelRange(dom, paddingTop) {
4620 let rect = dom.getBoundingClientRect();
4621 return { left: 0, right: rect.right - rect.left,
4622 top: paddingTop, bottom: rect.bottom - (rect.top + paddingTop) };
4623}
4624// Line gaps are placeholder widgets used to hide pieces of overlong
4625// lines within the viewport, as a kludge to keep the editor
4626// responsive when a ridiculously long line is loaded into it.
4627class LineGap {
4628 constructor(from, to, size) {
4629 this.from = from;
4630 this.to = to;
4631 this.size = size;
4632 }
4633 static same(a, b) {
4634 if (a.length != b.length)
4635 return false;
4636 for (let i = 0; i < a.length; i++) {
4637 let gA = a[i], gB = b[i];
4638 if (gA.from != gB.from || gA.to != gB.to || gA.size != gB.size)
4639 return false;
4640 }
4641 return true;
4642 }
4643 draw(wrapping) {
4644 return Decoration.replace({ widget: new LineGapWidget(this.size, wrapping) }).range(this.from, this.to);
4645 }
4646}
4647class LineGapWidget extends WidgetType {
4648 constructor(size, vertical) {
4649 super();
4650 this.size = size;
4651 this.vertical = vertical;
4652 }
4653 eq(other) { return other.size == this.size && other.vertical == this.vertical; }
4654 toDOM() {
4655 let elt = document.createElement("div");
4656 if (this.vertical) {
4657 elt.style.height = this.size + "px";
4658 }
4659 else {
4660 elt.style.width = this.size + "px";
4661 elt.style.height = "2px";
4662 elt.style.display = "inline-block";
4663 }
4664 return elt;
4665 }
4666 get estimatedHeight() { return this.vertical ? this.size : -1; }
4667}
4668class ViewState {
4669 constructor(state) {
4670 this.state = state;
4671 // These are contentDOM-local coordinates
4672 this.pixelViewport = { left: 0, right: window.innerWidth, top: 0, bottom: 0 };
4673 this.inView = true;
4674 this.paddingTop = 0;
4675 this.paddingBottom = 0;
4676 this.contentDOMWidth = 0;
4677 this.contentDOMHeight = 0;
4678 this.editorHeight = 0;
4679 this.editorWidth = 0;
4680 this.heightOracle = new HeightOracle;
4681 // See VP.MaxDOMHeight
4682 this.scaler = IdScaler;
4683 this.scrollTarget = null;
4684 // Briefly set to true when printing, to disable viewport limiting
4685 this.printing = false;
4686 // Flag set when editor content was redrawn, so that the next
4687 // measure stage knows it must read DOM layout
4688 this.mustMeasureContent = true;
4689 this.defaultTextDirection = Direction.RTL;
4690 this.visibleRanges = [];
4691 // Cursor 'assoc' is only significant when the cursor is on a line
4692 // wrap point, where it must stick to the character that it is
4693 // associated with. Since browsers don't provide a reasonable
4694 // interface to set or query this, when a selection is set that
4695 // might cause this to be significant, this flag is set. The next
4696 // measure phase will check whether the cursor is on a line-wrapping
4697 // boundary and, if so, reset it to make sure it is positioned in
4698 // the right place.
4699 this.mustEnforceCursorAssoc = false;
4700 this.stateDeco = state.facet(decorations).filter(d => typeof d != "function");
4701 this.heightMap = HeightMap.empty().applyChanges(this.stateDeco, Text.empty, this.heightOracle.setDoc(state.doc), [new ChangedRange(0, 0, 0, state.doc.length)]);
4702 this.viewport = this.getViewport(0, null);
4703 this.updateViewportLines();
4704 this.updateForViewport();
4705 this.lineGaps = this.ensureLineGaps([]);
4706 this.lineGapDeco = Decoration.set(this.lineGaps.map(gap => gap.draw(false)));
4707 this.computeVisibleRanges();
4708 }
4709 updateForViewport() {
4710 let viewports = [this.viewport], { main } = this.state.selection;
4711 for (let i = 0; i <= 1; i++) {
4712 let pos = i ? main.head : main.anchor;
4713 if (!viewports.some(({ from, to }) => pos >= from && pos <= to)) {
4714 let { from, to } = this.lineBlockAt(pos);
4715 viewports.push(new Viewport(from, to));
4716 }
4717 }
4718 this.viewports = viewports.sort((a, b) => a.from - b.from);
4719 this.scaler = this.heightMap.height <= 7000000 /* MaxDOMHeight */ ? IdScaler :
4720 new BigScaler(this.heightOracle.doc, this.heightMap, this.viewports);
4721 }
4722 updateViewportLines() {
4723 this.viewportLines = [];
4724 this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.state.doc, 0, 0, block => {
4725 this.viewportLines.push(this.scaler.scale == 1 ? block : scaleBlock(block, this.scaler));
4726 });
4727 }
4728 update(update, scrollTarget = null) {
4729 this.state = update.state;
4730 let prevDeco = this.stateDeco;
4731 this.stateDeco = this.state.facet(decorations).filter(d => typeof d != "function");
4732 let contentChanges = update.changedRanges;
4733 let heightChanges = ChangedRange.extendWithRanges(contentChanges, heightRelevantDecoChanges(prevDeco, this.stateDeco, update ? update.changes : ChangeSet.empty(this.state.doc.length)));
4734 let prevHeight = this.heightMap.height;
4735 this.heightMap = this.heightMap.applyChanges(this.stateDeco, update.startState.doc, this.heightOracle.setDoc(this.state.doc), heightChanges);
4736 if (this.heightMap.height != prevHeight)
4737 update.flags |= 2 /* Height */;
4738 let viewport = heightChanges.length ? this.mapViewport(this.viewport, update.changes) : this.viewport;
4739 if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
4740 !this.viewportIsAppropriate(viewport))
4741 viewport = this.getViewport(0, scrollTarget);
4742 let updateLines = !update.changes.empty || (update.flags & 2 /* Height */) ||
4743 viewport.from != this.viewport.from || viewport.to != this.viewport.to;
4744 this.viewport = viewport;
4745 this.updateForViewport();
4746 if (updateLines)
4747 this.updateViewportLines();
4748 if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4749 this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
4750 update.flags |= this.computeVisibleRanges();
4751 if (scrollTarget)
4752 this.scrollTarget = scrollTarget;
4753 if (!this.mustEnforceCursorAssoc && update.selectionSet && update.view.lineWrapping &&
4754 update.state.selection.main.empty && update.state.selection.main.assoc)
4755 this.mustEnforceCursorAssoc = true;
4756 }
4757 measure(view) {
4758 let dom = view.contentDOM, style = window.getComputedStyle(dom);
4759 let oracle = this.heightOracle;
4760 let whiteSpace = style.whiteSpace;
4761 this.defaultTextDirection = style.direction == "rtl" ? Direction.RTL : Direction.LTR;
4762 let refresh = this.heightOracle.mustRefreshForWrapping(whiteSpace);
4763 let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != dom.clientHeight;
4764 this.contentDOMHeight = dom.clientHeight;
4765 this.mustMeasureContent = false;
4766 let result = 0, bias = 0;
4767 // Vertical padding
4768 let paddingTop = parseInt(style.paddingTop) || 0, paddingBottom = parseInt(style.paddingBottom) || 0;
4769 if (this.paddingTop != paddingTop || this.paddingBottom != paddingBottom) {
4770 this.paddingTop = paddingTop;
4771 this.paddingBottom = paddingBottom;
4772 result |= 8 /* Geometry */ | 2 /* Height */;
4773 }
4774 if (this.editorWidth != view.scrollDOM.clientWidth) {
4775 if (oracle.lineWrapping)
4776 measureContent = true;
4777 this.editorWidth = view.scrollDOM.clientWidth;
4778 result |= 8 /* Geometry */;
4779 }
4780 // Pixel viewport
4781 let pixelViewport = (this.printing ? fullPixelRange : visiblePixelRange)(dom, this.paddingTop);
4782 let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
4783 this.pixelViewport = pixelViewport;
4784 let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
4785 if (inView != this.inView) {
4786 this.inView = inView;
4787 if (inView)
4788 measureContent = true;
4789 }
4790 if (!this.inView)
4791 return 0;
4792 let contentWidth = dom.clientWidth;
4793 if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight) {
4794 this.contentDOMWidth = contentWidth;
4795 this.editorHeight = view.scrollDOM.clientHeight;
4796 result |= 8 /* Geometry */;
4797 }
4798 if (measureContent) {
4799 let lineHeights = view.docView.measureVisibleLineHeights(this.viewport);
4800 if (oracle.mustRefreshForHeights(lineHeights))
4801 refresh = true;
4802 if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
4803 let { lineHeight, charWidth } = view.docView.measureTextSize();
4804 refresh = oracle.refresh(whiteSpace, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
4805 if (refresh) {
4806 view.docView.minWidth = 0;
4807 result |= 8 /* Geometry */;
4808 }
4809 }
4810 if (dTop > 0 && dBottom > 0)
4811 bias = Math.max(dTop, dBottom);
4812 else if (dTop < 0 && dBottom < 0)
4813 bias = Math.min(dTop, dBottom);
4814 oracle.heightChanged = false;
4815 for (let vp of this.viewports) {
4816 let heights = vp.from == this.viewport.from ? lineHeights : view.docView.measureVisibleLineHeights(vp);
4817 this.heightMap = this.heightMap.updateHeight(oracle, 0, refresh, new MeasuredHeights(vp.from, heights));
4818 }
4819 if (oracle.heightChanged)
4820 result |= 2 /* Height */;
4821 }
4822 let viewportChange = !this.viewportIsAppropriate(this.viewport, bias) ||
4823 this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to);
4824 if (viewportChange)
4825 this.viewport = this.getViewport(bias, this.scrollTarget);
4826 this.updateForViewport();
4827 if ((result & 2 /* Height */) || viewportChange)
4828 this.updateViewportLines();
4829 if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4830 this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
4831 result |= this.computeVisibleRanges();
4832 if (this.mustEnforceCursorAssoc) {
4833 this.mustEnforceCursorAssoc = false;
4834 // This is done in the read stage, because moving the selection
4835 // to a line end is going to trigger a layout anyway, so it
4836 // can't be a pure write. It should be rare that it does any
4837 // writing.
4838 view.docView.enforceCursorAssoc();
4839 }
4840 return result;
4841 }
4842 get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top); }
4843 get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom); }
4844 getViewport(bias, scrollTarget) {
4845 // This will divide VP.Margin between the top and the
4846 // bottom, depending on the bias (the change in viewport position
4847 // since the last update). It'll hold a number between 0 and 1
4848 let marginTop = 0.5 - Math.max(-0.5, Math.min(0.5, bias / 1000 /* Margin */ / 2));
4849 let map = this.heightMap, doc = this.state.doc, { visibleTop, visibleBottom } = this;
4850 let viewport = new Viewport(map.lineAt(visibleTop - marginTop * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).from, map.lineAt(visibleBottom + (1 - marginTop) * 1000 /* Margin */, QueryType.ByHeight, doc, 0, 0).to);
4851 // If scrollTarget is given, make sure the viewport includes that position
4852 if (scrollTarget) {
4853 let { head } = scrollTarget.range;
4854 if (head < viewport.from || head > viewport.to) {
4855 let viewHeight = Math.min(this.editorHeight, this.pixelViewport.bottom - this.pixelViewport.top);
4856 let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4857 if (scrollTarget.y == "center")
4858 topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
4859 else if (scrollTarget.y == "start" || scrollTarget.y == "nearest" && head < viewport.from)
4860 topPos = block.top;
4861 else
4862 topPos = block.bottom - viewHeight;
4863 viewport = new Viewport(map.lineAt(topPos - 1000 /* Margin */ / 2, QueryType.ByHeight, doc, 0, 0).from, map.lineAt(topPos + viewHeight + 1000 /* Margin */ / 2, QueryType.ByHeight, doc, 0, 0).to);
4864 }
4865 }
4866 return viewport;
4867 }
4868 mapViewport(viewport, changes) {
4869 let from = changes.mapPos(viewport.from, -1), to = changes.mapPos(viewport.to, 1);
4870 return new Viewport(this.heightMap.lineAt(from, QueryType.ByPos, this.state.doc, 0, 0).from, this.heightMap.lineAt(to, QueryType.ByPos, this.state.doc, 0, 0).to);
4871 }
4872 // Checks if a given viewport covers the visible part of the
4873 // document and not too much beyond that.
4874 viewportIsAppropriate({ from, to }, bias = 0) {
4875 if (!this.inView)
4876 return true;
4877 let { top } = this.heightMap.lineAt(from, QueryType.ByPos, this.state.doc, 0, 0);
4878 let { bottom } = this.heightMap.lineAt(to, QueryType.ByPos, this.state.doc, 0, 0);
4879 let { visibleTop, visibleBottom } = this;
4880 return (from == 0 || top <= visibleTop - Math.max(10 /* MinCoverMargin */, Math.min(-bias, 250 /* MaxCoverMargin */))) &&
4881 (to == this.state.doc.length ||
4882 bottom >= visibleBottom + Math.max(10 /* MinCoverMargin */, Math.min(bias, 250 /* MaxCoverMargin */))) &&
4883 (top > visibleTop - 2 * 1000 /* Margin */ && bottom < visibleBottom + 2 * 1000 /* Margin */);
4884 }
4885 mapLineGaps(gaps, changes) {
4886 if (!gaps.length || changes.empty)
4887 return gaps;
4888 let mapped = [];
4889 for (let gap of gaps)
4890 if (!changes.touchesRange(gap.from, gap.to))
4891 mapped.push(new LineGap(changes.mapPos(gap.from), changes.mapPos(gap.to), gap.size));
4892 return mapped;
4893 }
4894 // Computes positions in the viewport where the start or end of a
4895 // line should be hidden, trying to reuse existing line gaps when
4896 // appropriate to avoid unneccesary redraws.
4897 // Uses crude character-counting for the positioning and sizing,
4898 // since actual DOM coordinates aren't always available and
4899 // predictable. Relies on generous margins (see LG.Margin) to hide
4900 // the artifacts this might produce from the user.
4901 ensureLineGaps(current) {
4902 let gaps = [];
4903 // This won't work at all in predominantly right-to-left text.
4904 if (this.defaultTextDirection != Direction.LTR)
4905 return gaps;
4906 for (let line of this.viewportLines) {
4907 if (line.length < 4000 /* DoubleMargin */)
4908 continue;
4909 let structure = lineStructure(line.from, line.to, this.stateDeco);
4910 if (structure.total < 4000 /* DoubleMargin */)
4911 continue;
4912 let viewFrom, viewTo;
4913 if (this.heightOracle.lineWrapping) {
4914 let marginHeight = (2000 /* Margin */ / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
4915 viewFrom = findPosition(structure, (this.visibleTop - line.top - marginHeight) / line.height);
4916 viewTo = findPosition(structure, (this.visibleBottom - line.top + marginHeight) / line.height);
4917 }
4918 else {
4919 let totalWidth = structure.total * this.heightOracle.charWidth;
4920 let marginWidth = 2000 /* Margin */ * this.heightOracle.charWidth;
4921 viewFrom = findPosition(structure, (this.pixelViewport.left - marginWidth) / totalWidth);
4922 viewTo = findPosition(structure, (this.pixelViewport.right + marginWidth) / totalWidth);
4923 }
4924 let outside = [];
4925 if (viewFrom > line.from)
4926 outside.push({ from: line.from, to: viewFrom });
4927 if (viewTo < line.to)
4928 outside.push({ from: viewTo, to: line.to });
4929 let sel = this.state.selection.main;
4930 // Make sure the gaps don't cover a selection end
4931 if (sel.from >= line.from && sel.from <= line.to)
4932 cutRange(outside, sel.from - 10 /* SelectionMargin */, sel.from + 10 /* SelectionMargin */);
4933 if (!sel.empty && sel.to >= line.from && sel.to <= line.to)
4934 cutRange(outside, sel.to - 10 /* SelectionMargin */, sel.to + 10 /* SelectionMargin */);
4935 for (let { from, to } of outside)
4936 if (to - from > 1000 /* HalfMargin */) {
4937 gaps.push(find(current, gap => gap.from >= line.from && gap.to <= line.to &&
4938 Math.abs(gap.from - from) < 1000 /* HalfMargin */ && Math.abs(gap.to - to) < 1000 /* HalfMargin */) ||
4939 new LineGap(from, to, this.gapSize(line, from, to, structure)));
4940 }
4941 }
4942 return gaps;
4943 }
4944 gapSize(line, from, to, structure) {
4945 let fraction = findFraction(structure, to) - findFraction(structure, from);
4946 if (this.heightOracle.lineWrapping) {
4947 return line.height * fraction;
4948 }
4949 else {
4950 return structure.total * this.heightOracle.charWidth * fraction;
4951 }
4952 }
4953 updateLineGaps(gaps) {
4954 if (!LineGap.same(gaps, this.lineGaps)) {
4955 this.lineGaps = gaps;
4956 this.lineGapDeco = Decoration.set(gaps.map(gap => gap.draw(this.heightOracle.lineWrapping)));
4957 }
4958 }
4959 computeVisibleRanges() {
4960 let deco = this.stateDeco;
4961 if (this.lineGaps.length)
4962 deco = deco.concat(this.lineGapDeco);
4963 let ranges = [];
4964 RangeSet.spans(deco, this.viewport.from, this.viewport.to, {
4965 span(from, to) { ranges.push({ from, to }); },
4966 point() { }
4967 }, 20);
4968 let changed = ranges.length != this.visibleRanges.length ||
4969 this.visibleRanges.some((r, i) => r.from != ranges[i].from || r.to != ranges[i].to);
4970 this.visibleRanges = ranges;
4971 return changed ? 4 /* Viewport */ : 0;
4972 }
4973 lineBlockAt(pos) {
4974 return (pos >= this.viewport.from && pos <= this.viewport.to && this.viewportLines.find(b => b.from <= pos && b.to >= pos)) ||
4975 scaleBlock(this.heightMap.lineAt(pos, QueryType.ByPos, this.state.doc, 0, 0), this.scaler);
4976 }
4977 lineBlockAtHeight(height) {
4978 return scaleBlock(this.heightMap.lineAt(this.scaler.fromDOM(height), QueryType.ByHeight, this.state.doc, 0, 0), this.scaler);
4979 }
4980 elementAtHeight(height) {
4981 return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height), this.state.doc, 0, 0), this.scaler);
4982 }
4983 get docHeight() {
4984 return this.scaler.toDOM(this.heightMap.height);
4985 }
4986 get contentHeight() {
4987 return this.docHeight + this.paddingTop + this.paddingBottom;
4988 }
4989}
4990class Viewport {
4991 constructor(from, to) {
4992 this.from = from;
4993 this.to = to;
4994 }
4995}
4996function lineStructure(from, to, stateDeco) {
4997 let ranges = [], pos = from, total = 0;
4998 RangeSet.spans(stateDeco, from, to, {
4999 span() { },
5000 point(from, to) {
5001 if (from > pos) {
5002 ranges.push({ from: pos, to: from });
5003 total += from - pos;
5004 }
5005 pos = to;
5006 }
5007 }, 20); // We're only interested in collapsed ranges of a significant size
5008 if (pos < to) {
5009 ranges.push({ from: pos, to });
5010 total += to - pos;
5011 }
5012 return { total, ranges };
5013}
5014function findPosition({ total, ranges }, ratio) {
5015 if (ratio <= 0)
5016 return ranges[0].from;
5017 if (ratio >= 1)
5018 return ranges[ranges.length - 1].to;
5019 let dist = Math.floor(total * ratio);
5020 for (let i = 0;; i++) {
5021 let { from, to } = ranges[i], size = to - from;
5022 if (dist <= size)
5023 return from + dist;
5024 dist -= size;
5025 }
5026}
5027function findFraction(structure, pos) {
5028 let counted = 0;
5029 for (let { from, to } of structure.ranges) {
5030 if (pos <= to) {
5031 counted += pos - from;
5032 break;
5033 }
5034 counted += to - from;
5035 }
5036 return counted / structure.total;
5037}
5038function cutRange(ranges, from, to) {
5039 for (let i = 0; i < ranges.length; i++) {
5040 let r = ranges[i];
5041 if (r.from < to && r.to > from) {
5042 let pieces = [];
5043 if (r.from < from)
5044 pieces.push({ from: r.from, to: from });
5045 if (r.to > to)
5046 pieces.push({ from: to, to: r.to });
5047 ranges.splice(i, 1, ...pieces);
5048 i += pieces.length - 1;
5049 }
5050 }
5051}
5052function find(array, f) {
5053 for (let val of array)
5054 if (f(val))
5055 return val;
5056 return undefined;
5057}
5058// Don't scale when the document height is within the range of what
5059// the DOM can handle.
5060const IdScaler = {
5061 toDOM(n) { return n; },
5062 fromDOM(n) { return n; },
5063 scale: 1
5064};
5065// When the height is too big (> VP.MaxDOMHeight), scale down the
5066// regions outside the viewports so that the total height is
5067// VP.MaxDOMHeight.
5068class BigScaler {
5069 constructor(doc, heightMap, viewports) {
5070 let vpHeight = 0, base = 0, domBase = 0;
5071 this.viewports = viewports.map(({ from, to }) => {
5072 let top = heightMap.lineAt(from, QueryType.ByPos, doc, 0, 0).top;
5073 let bottom = heightMap.lineAt(to, QueryType.ByPos, doc, 0, 0).bottom;
5074 vpHeight += bottom - top;
5075 return { from, to, top, bottom, domTop: 0, domBottom: 0 };
5076 });
5077 this.scale = (7000000 /* MaxDOMHeight */ - vpHeight) / (heightMap.height - vpHeight);
5078 for (let obj of this.viewports) {
5079 obj.domTop = domBase + (obj.top - base) * this.scale;
5080 domBase = obj.domBottom = obj.domTop + (obj.bottom - obj.top);
5081 base = obj.bottom;
5082 }
5083 }
5084 toDOM(n) {
5085 for (let i = 0, base = 0, domBase = 0;; i++) {
5086 let vp = i < this.viewports.length ? this.viewports[i] : null;
5087 if (!vp || n < vp.top)
5088 return domBase + (n - base) * this.scale;
5089 if (n <= vp.bottom)
5090 return vp.domTop + (n - vp.top);
5091 base = vp.bottom;
5092 domBase = vp.domBottom;
5093 }
5094 }
5095 fromDOM(n) {
5096 for (let i = 0, base = 0, domBase = 0;; i++) {
5097 let vp = i < this.viewports.length ? this.viewports[i] : null;
5098 if (!vp || n < vp.domTop)
5099 return base + (n - domBase) / this.scale;
5100 if (n <= vp.domBottom)
5101 return vp.top + (n - vp.domTop);
5102 base = vp.bottom;
5103 domBase = vp.domBottom;
5104 }
5105 }
5106}
5107function scaleBlock(block, scaler) {
5108 if (scaler.scale == 1)
5109 return block;
5110 let bTop = scaler.toDOM(block.top), bBottom = scaler.toDOM(block.bottom);
5111 return new BlockInfo(block.from, block.length, bTop, bBottom - bTop, Array.isArray(block.type) ? block.type.map(b => scaleBlock(b, scaler)) : block.type);
5112}
5113
5114const theme = /*@__PURE__*/Facet.define({ combine: strs => strs.join(" ") });
5115const darkTheme = /*@__PURE__*/Facet.define({ combine: values => values.indexOf(true) > -1 });
5116const baseThemeID = /*@__PURE__*/StyleModule.newName(), baseLightID = /*@__PURE__*/StyleModule.newName(), baseDarkID = /*@__PURE__*/StyleModule.newName();
5117const lightDarkIDs = { "&light": "." + baseLightID, "&dark": "." + baseDarkID };
5118function buildTheme(main, spec, scopes) {
5119 return new StyleModule(spec, {
5120 finish(sel) {
5121 return /&/.test(sel) ? sel.replace(/&\w*/, m => {
5122 if (m == "&")
5123 return main;
5124 if (!scopes || !scopes[m])
5125 throw new RangeError(`Unsupported selector: ${m}`);
5126 return scopes[m];
5127 }) : main + " " + sel;
5128 }
5129 });
5130}
5131const baseTheme$1 = /*@__PURE__*/buildTheme("." + baseThemeID, {
5132 "&.cm-editor": {
5133 position: "relative !important",
5134 boxSizing: "border-box",
5135 "&.cm-focused": {
5136 // Provide a simple default outline to make sure a focused
5137 // editor is visually distinct. Can't leave the default behavior
5138 // because that will apply to the content element, which is
5139 // inside the scrollable container and doesn't include the
5140 // gutters. We also can't use an 'auto' outline, since those
5141 // are, for some reason, drawn behind the element content, which
5142 // will cause things like the active line background to cover
5143 // the outline (#297).
5144 outline: "1px dotted #212121"
5145 },
5146 display: "flex !important",
5147 flexDirection: "column"
5148 },
5149 ".cm-scroller": {
5150 display: "flex !important",
5151 alignItems: "flex-start !important",
5152 fontFamily: "monospace",
5153 lineHeight: 1.4,
5154 height: "100%",
5155 overflowX: "auto",
5156 position: "relative",
5157 zIndex: 0
5158 },
5159 ".cm-content": {
5160 margin: 0,
5161 flexGrow: 2,
5162 minHeight: "100%",
5163 display: "block",
5164 whiteSpace: "pre",
5165 wordWrap: "normal",
5166 boxSizing: "border-box",
5167 padding: "4px 0",
5168 outline: "none",
5169 "&[contenteditable=true]": {
5170 WebkitUserModify: "read-write-plaintext-only",
5171 }
5172 },
5173 ".cm-lineWrapping": {
5174 whiteSpace_fallback: "pre-wrap",
5175 whiteSpace: "break-spaces",
5176 wordBreak: "break-word",
5177 overflowWrap: "anywhere"
5178 },
5179 "&light .cm-content": { caretColor: "black" },
5180 "&dark .cm-content": { caretColor: "white" },
5181 ".cm-line": {
5182 display: "block",
5183 padding: "0 2px 0 4px"
5184 },
5185 ".cm-selectionLayer": {
5186 zIndex: -1,
5187 contain: "size style"
5188 },
5189 ".cm-selectionBackground": {
5190 position: "absolute",
5191 },
5192 "&light .cm-selectionBackground": {
5193 background: "#d9d9d9"
5194 },
5195 "&dark .cm-selectionBackground": {
5196 background: "#222"
5197 },
5198 "&light.cm-focused .cm-selectionBackground": {
5199 background: "#d7d4f0"
5200 },
5201 "&dark.cm-focused .cm-selectionBackground": {
5202 background: "#233"
5203 },
5204 ".cm-cursorLayer": {
5205 zIndex: 100,
5206 contain: "size style",
5207 pointerEvents: "none"
5208 },
5209 "&.cm-focused .cm-cursorLayer": {
5210 animation: "steps(1) cm-blink 1.2s infinite"
5211 },
5212 // Two animations defined so that we can switch between them to
5213 // restart the animation without forcing another style
5214 // recomputation.
5215 "@keyframes cm-blink": { "0%": {}, "50%": { visibility: "hidden" }, "100%": {} },
5216 "@keyframes cm-blink2": { "0%": {}, "50%": { visibility: "hidden" }, "100%": {} },
5217 ".cm-cursor, .cm-dropCursor": {
5218 position: "absolute",
5219 borderLeft: "1.2px solid black",
5220 marginLeft: "-0.6px",
5221 pointerEvents: "none",
5222 },
5223 ".cm-cursor": {
5224 display: "none"
5225 },
5226 "&dark .cm-cursor": {
5227 borderLeftColor: "#444"
5228 },
5229 "&.cm-focused .cm-cursor": {
5230 display: "block"
5231 },
5232 "&light .cm-activeLine": { backgroundColor: "#f3f9ff" },
5233 "&dark .cm-activeLine": { backgroundColor: "#223039" },
5234 "&light .cm-specialChar": { color: "red" },
5235 "&dark .cm-specialChar": { color: "#f78" },
5236 ".cm-gutters": {
5237 display: "flex",
5238 height: "100%",
5239 boxSizing: "border-box",
5240 left: 0,
5241 zIndex: 200
5242 },
5243 "&light .cm-gutters": {
5244 backgroundColor: "#f5f5f5",
5245 color: "#6c6c6c",
5246 borderRight: "1px solid #ddd"
5247 },
5248 "&dark .cm-gutters": {
5249 backgroundColor: "#333338",
5250 color: "#ccc"
5251 },
5252 ".cm-gutter": {
5253 display: "flex !important",
5254 flexDirection: "column",
5255 flexShrink: 0,
5256 boxSizing: "border-box",
5257 minHeight: "100%",
5258 overflow: "hidden"
5259 },
5260 ".cm-gutterElement": {
5261 boxSizing: "border-box"
5262 },
5263 ".cm-lineNumbers .cm-gutterElement": {
5264 padding: "0 3px 0 5px",
5265 minWidth: "20px",
5266 textAlign: "right",
5267 whiteSpace: "nowrap"
5268 },
5269 "&light .cm-activeLineGutter": {
5270 backgroundColor: "#e2f2ff"
5271 },
5272 "&dark .cm-activeLineGutter": {
5273 backgroundColor: "#222227"
5274 },
5275 ".cm-panels": {
5276 boxSizing: "border-box",
5277 position: "sticky",
5278 left: 0,
5279 right: 0
5280 },
5281 "&light .cm-panels": {
5282 backgroundColor: "#f5f5f5",
5283 color: "black"
5284 },
5285 "&light .cm-panels-top": {
5286 borderBottom: "1px solid #ddd"
5287 },
5288 "&light .cm-panels-bottom": {
5289 borderTop: "1px solid #ddd"
5290 },
5291 "&dark .cm-panels": {
5292 backgroundColor: "#333338",
5293 color: "white"
5294 },
5295 ".cm-tab": {
5296 display: "inline-block",
5297 overflow: "hidden",
5298 verticalAlign: "bottom"
5299 },
5300 ".cm-widgetBuffer": {
5301 verticalAlign: "text-top",
5302 height: "1em",
5303 display: "inline"
5304 },
5305 ".cm-placeholder": {
5306 color: "#888",
5307 display: "inline-block",
5308 verticalAlign: "top",
5309 },
5310 ".cm-button": {
5311 verticalAlign: "middle",
5312 color: "inherit",
5313 fontSize: "70%",
5314 padding: ".2em 1em",
5315 borderRadius: "1px"
5316 },
5317 "&light .cm-button": {
5318 backgroundImage: "linear-gradient(#eff1f5, #d9d9df)",
5319 border: "1px solid #888",
5320 "&:active": {
5321 backgroundImage: "linear-gradient(#b4b4b4, #d0d3d6)"
5322 }
5323 },
5324 "&dark .cm-button": {
5325 backgroundImage: "linear-gradient(#393939, #111)",
5326 border: "1px solid #888",
5327 "&:active": {
5328 backgroundImage: "linear-gradient(#111, #333)"
5329 }
5330 },
5331 ".cm-textfield": {
5332 verticalAlign: "middle",
5333 color: "inherit",
5334 fontSize: "70%",
5335 border: "1px solid silver",
5336 padding: ".2em .5em"
5337 },
5338 "&light .cm-textfield": {
5339 backgroundColor: "white"
5340 },
5341 "&dark .cm-textfield": {
5342 border: "1px solid #555",
5343 backgroundColor: "inherit"
5344 }
5345}, lightDarkIDs);
5346
5347const observeOptions = {
5348 childList: true,
5349 characterData: true,
5350 subtree: true,
5351 attributes: true,
5352 characterDataOldValue: true
5353};
5354// IE11 has very broken mutation observers, so we also listen to
5355// DOMCharacterDataModified there
5356const useCharData = browser.ie && browser.ie_version <= 11;
5357class DOMObserver {
5358 constructor(view, onChange, onScrollChanged) {
5359 this.view = view;
5360 this.onChange = onChange;
5361 this.onScrollChanged = onScrollChanged;
5362 this.active = false;
5363 // The known selection. Kept in our own object, as opposed to just
5364 // directly accessing the selection because:
5365 // - Safari doesn't report the right selection in shadow DOM
5366 // - Reading from the selection forces a DOM layout
5367 // - This way, we can ignore selectionchange events if we have
5368 // already seen the 'new' selection
5369 this.selectionRange = new DOMSelectionState;
5370 // Set when a selection change is detected, cleared on flush
5371 this.selectionChanged = false;
5372 this.delayedFlush = -1;
5373 this.resizeTimeout = -1;
5374 this.queue = [];
5375 this.delayedAndroidKey = null;
5376 this.scrollTargets = [];
5377 this.intersection = null;
5378 this.resize = null;
5379 this.intersecting = false;
5380 this.gapIntersection = null;
5381 this.gaps = [];
5382 // Timeout for scheduling check of the parents that need scroll handlers
5383 this.parentCheck = -1;
5384 this.dom = view.contentDOM;
5385 this.observer = new MutationObserver(mutations => {
5386 for (let mut of mutations)
5387 this.queue.push(mut);
5388 // IE11 will sometimes (on typing over a selection or
5389 // backspacing out a single character text node) call the
5390 // observer callback before actually updating the DOM.
5391 //
5392 // Unrelatedly, iOS Safari will, when ending a composition,
5393 // sometimes first clear it, deliver the mutations, and then
5394 // reinsert the finished text. CodeMirror's handling of the
5395 // deletion will prevent the reinsertion from happening,
5396 // breaking composition.
5397 if ((browser.ie && browser.ie_version <= 11 || browser.ios && view.composing) &&
5398 mutations.some(m => m.type == "childList" && m.removedNodes.length ||
5399 m.type == "characterData" && m.oldValue.length > m.target.nodeValue.length))
5400 this.flushSoon();
5401 else
5402 this.flush();
5403 });
5404 if (useCharData)
5405 this.onCharData = (event) => {
5406 this.queue.push({ target: event.target,
5407 type: "characterData",
5408 oldValue: event.prevValue });
5409 this.flushSoon();
5410 };
5411 this.onSelectionChange = this.onSelectionChange.bind(this);
5412 window.addEventListener("resize", this.onResize = this.onResize.bind(this));
5413 if (typeof ResizeObserver == "function") {
5414 this.resize = new ResizeObserver(() => {
5415 if (this.view.docView.lastUpdate < Date.now() - 75)
5416 this.onResize();
5417 });
5418 this.resize.observe(view.scrollDOM);
5419 }
5420 window.addEventListener("beforeprint", this.onPrint = this.onPrint.bind(this));
5421 this.start();
5422 window.addEventListener("scroll", this.onScroll = this.onScroll.bind(this));
5423 if (typeof IntersectionObserver == "function") {
5424 this.intersection = new IntersectionObserver(entries => {
5425 if (this.parentCheck < 0)
5426 this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1000);
5427 if (entries.length > 0 && (entries[entries.length - 1].intersectionRatio > 0) != this.intersecting) {
5428 this.intersecting = !this.intersecting;
5429 if (this.intersecting != this.view.inView)
5430 this.onScrollChanged(document.createEvent("Event"));
5431 }
5432 }, {});
5433 this.intersection.observe(this.dom);
5434 this.gapIntersection = new IntersectionObserver(entries => {
5435 if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0)
5436 this.onScrollChanged(document.createEvent("Event"));
5437 }, {});
5438 }
5439 this.listenForScroll();
5440 this.readSelectionRange();
5441 this.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange);
5442 }
5443 onScroll(e) {
5444 if (this.intersecting)
5445 this.flush(false);
5446 this.onScrollChanged(e);
5447 }
5448 onResize() {
5449 if (this.resizeTimeout < 0)
5450 this.resizeTimeout = setTimeout(() => {
5451 this.resizeTimeout = -1;
5452 this.view.requestMeasure();
5453 }, 50);
5454 }
5455 onPrint() {
5456 this.view.viewState.printing = true;
5457 this.view.measure();
5458 setTimeout(() => {
5459 this.view.viewState.printing = false;
5460 this.view.requestMeasure();
5461 }, 500);
5462 }
5463 updateGaps(gaps) {
5464 if (this.gapIntersection && (gaps.length != this.gaps.length || this.gaps.some((g, i) => g != gaps[i]))) {
5465 this.gapIntersection.disconnect();
5466 for (let gap of gaps)
5467 this.gapIntersection.observe(gap);
5468 this.gaps = gaps;
5469 }
5470 }
5471 onSelectionChange(event) {
5472 if (!this.readSelectionRange() || this.delayedAndroidKey)
5473 return;
5474 let { view } = this, sel = this.selectionRange;
5475 if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
5476 return;
5477 let context = sel.anchorNode && view.docView.nearest(sel.anchorNode);
5478 if (context && context.ignoreEvent(event))
5479 return;
5480 // Deletions on IE11 fire their events in the wrong order, giving
5481 // us a selection change event before the DOM changes are
5482 // reported.
5483 // Chrome Android has a similar issue when backspacing out a
5484 // selection (#645).
5485 if ((browser.ie && browser.ie_version <= 11 || browser.android && browser.chrome) && !view.state.selection.main.empty &&
5486 // (Selection.isCollapsed isn't reliable on IE)
5487 sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset))
5488 this.flushSoon();
5489 else
5490 this.flush(false);
5491 }
5492 readSelectionRange() {
5493 let { root } = this.view;
5494 // The Selection object is broken in shadow roots in Safari. See
5495 // https://github.com/codemirror/dev/issues/414
5496 let range = browser.safari && root.nodeType == 11 && deepActiveElement() == this.view.contentDOM &&
5497 safariSelectionRangeHack(this.view) || getSelection(root);
5498 if (!range || this.selectionRange.eq(range))
5499 return false;
5500 this.selectionRange.setRange(range);
5501 return this.selectionChanged = true;
5502 }
5503 setSelectionRange(anchor, head) {
5504 this.selectionRange.set(anchor.node, anchor.offset, head.node, head.offset);
5505 this.selectionChanged = false;
5506 }
5507 clearSelectionRange() {
5508 this.selectionRange.set(null, 0, null, 0);
5509 }
5510 listenForScroll() {
5511 this.parentCheck = -1;
5512 let i = 0, changed = null;
5513 for (let dom = this.dom; dom;) {
5514 if (dom.nodeType == 1) {
5515 if (!changed && i < this.scrollTargets.length && this.scrollTargets[i] == dom)
5516 i++;
5517 else if (!changed)
5518 changed = this.scrollTargets.slice(0, i);
5519 if (changed)
5520 changed.push(dom);
5521 dom = dom.assignedSlot || dom.parentNode;
5522 }
5523 else if (dom.nodeType == 11) { // Shadow root
5524 dom = dom.host;
5525 }
5526 else {
5527 break;
5528 }
5529 }
5530 if (i < this.scrollTargets.length && !changed)
5531 changed = this.scrollTargets.slice(0, i);
5532 if (changed) {
5533 for (let dom of this.scrollTargets)
5534 dom.removeEventListener("scroll", this.onScroll);
5535 for (let dom of this.scrollTargets = changed)
5536 dom.addEventListener("scroll", this.onScroll);
5537 }
5538 }
5539 ignore(f) {
5540 if (!this.active)
5541 return f();
5542 try {
5543 this.stop();
5544 return f();
5545 }
5546 finally {
5547 this.start();
5548 this.clear();
5549 }
5550 }
5551 start() {
5552 if (this.active)
5553 return;
5554 this.observer.observe(this.dom, observeOptions);
5555 if (useCharData)
5556 this.dom.addEventListener("DOMCharacterDataModified", this.onCharData);
5557 this.active = true;
5558 }
5559 stop() {
5560 if (!this.active)
5561 return;
5562 this.active = false;
5563 this.observer.disconnect();
5564 if (useCharData)
5565 this.dom.removeEventListener("DOMCharacterDataModified", this.onCharData);
5566 }
5567 // Throw away any pending changes
5568 clear() {
5569 this.processRecords();
5570 this.queue.length = 0;
5571 this.selectionChanged = false;
5572 }
5573 // Chrome Android, especially in combination with GBoard, not only
5574 // doesn't reliably fire regular key events, but also often
5575 // surrounds the effect of enter or backspace with a bunch of
5576 // composition events that, when interrupted, cause text duplication
5577 // or other kinds of corruption. This hack makes the editor back off
5578 // from handling DOM changes for a moment when such a key is
5579 // detected (via beforeinput or keydown), and then tries to flush
5580 // them or, if that has no effect, dispatches the given key.
5581 delayAndroidKey(key, keyCode) {
5582 if (!this.delayedAndroidKey)
5583 requestAnimationFrame(() => {
5584 let key = this.delayedAndroidKey;
5585 this.delayedAndroidKey = null;
5586 this.delayedFlush = -1;
5587 if (!this.flush())
5588 dispatchKey(this.view.contentDOM, key.key, key.keyCode);
5589 });
5590 // Since backspace beforeinput is sometimes signalled spuriously,
5591 // Enter always takes precedence.
5592 if (!this.delayedAndroidKey || key == "Enter")
5593 this.delayedAndroidKey = { key, keyCode };
5594 }
5595 flushSoon() {
5596 if (this.delayedFlush < 0)
5597 this.delayedFlush = window.setTimeout(() => { this.delayedFlush = -1; this.flush(); }, 20);
5598 }
5599 forceFlush() {
5600 if (this.delayedFlush >= 0) {
5601 window.clearTimeout(this.delayedFlush);
5602 this.delayedFlush = -1;
5603 this.flush();
5604 }
5605 }
5606 processRecords() {
5607 let records = this.queue;
5608 for (let mut of this.observer.takeRecords())
5609 records.push(mut);
5610 if (records.length)
5611 this.queue = [];
5612 let from = -1, to = -1, typeOver = false;
5613 for (let record of records) {
5614 let range = this.readMutation(record);
5615 if (!range)
5616 continue;
5617 if (range.typeOver)
5618 typeOver = true;
5619 if (from == -1) {
5620 ({ from, to } = range);
5621 }
5622 else {
5623 from = Math.min(range.from, from);
5624 to = Math.max(range.to, to);
5625 }
5626 }
5627 return { from, to, typeOver };
5628 }
5629 // Apply pending changes, if any
5630 flush(readSelection = true) {
5631 // Completely hold off flushing when pending keys are set—the code
5632 // managing those will make sure processRecords is called and the
5633 // view is resynchronized after
5634 if (this.delayedFlush >= 0 || this.delayedAndroidKey)
5635 return;
5636 if (readSelection)
5637 this.readSelectionRange();
5638 let { from, to, typeOver } = this.processRecords();
5639 let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5640 if (from < 0 && !newSel)
5641 return;
5642 this.selectionChanged = false;
5643 let startState = this.view.state;
5644 let handled = this.onChange(from, to, typeOver);
5645 // The view wasn't updated
5646 if (this.view.state == startState)
5647 this.view.update([]);
5648 return handled;
5649 }
5650 readMutation(rec) {
5651 let cView = this.view.docView.nearest(rec.target);
5652 if (!cView || cView.ignoreMutation(rec))
5653 return null;
5654 cView.markDirty(rec.type == "attributes");
5655 if (rec.type == "attributes")
5656 cView.dirty |= 4 /* Attrs */;
5657 if (rec.type == "childList") {
5658 let childBefore = findChild(cView, rec.previousSibling || rec.target.previousSibling, -1);
5659 let childAfter = findChild(cView, rec.nextSibling || rec.target.nextSibling, 1);
5660 return { from: childBefore ? cView.posAfter(childBefore) : cView.posAtStart,
5661 to: childAfter ? cView.posBefore(childAfter) : cView.posAtEnd, typeOver: false };
5662 }
5663 else if (rec.type == "characterData") {
5664 return { from: cView.posAtStart, to: cView.posAtEnd, typeOver: rec.target.nodeValue == rec.oldValue };
5665 }
5666 else {
5667 return null;
5668 }
5669 }
5670 destroy() {
5671 var _a, _b, _c;
5672 this.stop();
5673 (_a = this.intersection) === null || _a === void 0 ? void 0 : _a.disconnect();
5674 (_b = this.gapIntersection) === null || _b === void 0 ? void 0 : _b.disconnect();
5675 (_c = this.resize) === null || _c === void 0 ? void 0 : _c.disconnect();
5676 for (let dom of this.scrollTargets)
5677 dom.removeEventListener("scroll", this.onScroll);
5678 window.removeEventListener("scroll", this.onScroll);
5679 window.removeEventListener("resize", this.onResize);
5680 window.removeEventListener("beforeprint", this.onPrint);
5681 this.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
5682 clearTimeout(this.parentCheck);
5683 clearTimeout(this.resizeTimeout);
5684 }
5685}
5686function findChild(cView, dom, dir) {
5687 while (dom) {
5688 let curView = ContentView.get(dom);
5689 if (curView && curView.parent == cView)
5690 return curView;
5691 let parent = dom.parentNode;
5692 dom = parent != cView.dom ? parent : dir > 0 ? dom.nextSibling : dom.previousSibling;
5693 }
5694 return null;
5695}
5696// Used to work around a Safari Selection/shadow DOM bug (#414)
5697function safariSelectionRangeHack(view) {
5698 let found = null;
5699 // Because Safari (at least in 2018-2021) doesn't provide regular
5700 // access to the selection inside a shadowroot, we have to perform a
5701 // ridiculous hack to get at it—using `execCommand` to trigger a
5702 // `beforeInput` event so that we can read the target range from the
5703 // event.
5704 function read(event) {
5705 event.preventDefault();
5706 event.stopImmediatePropagation();
5707 found = event.getTargetRanges()[0];
5708 }
5709 view.contentDOM.addEventListener("beforeinput", read, true);
5710 document.execCommand("indent");
5711 view.contentDOM.removeEventListener("beforeinput", read, true);
5712 if (!found)
5713 return null;
5714 let anchorNode = found.startContainer, anchorOffset = found.startOffset;
5715 let focusNode = found.endContainer, focusOffset = found.endOffset;
5716 let curAnchor = view.docView.domAtPos(view.state.selection.main.anchor);
5717 // Since such a range doesn't distinguish between anchor and head,
5718 // use a heuristic that flips it around if its end matches the
5719 // current anchor.
5720 if (isEquivalentPosition(curAnchor.node, curAnchor.offset, focusNode, focusOffset))
5721 [anchorNode, anchorOffset, focusNode, focusOffset] = [focusNode, focusOffset, anchorNode, anchorOffset];
5722 return { anchorNode, anchorOffset, focusNode, focusOffset };
5723}
5724
5725function applyDOMChange(view, start, end, typeOver) {
5726 let change, newSel;
5727 let sel = view.state.selection.main;
5728 if (start > -1) {
5729 let bounds = view.docView.domBoundsAround(start, end, 0);
5730 if (!bounds || view.state.readOnly)
5731 return false;
5732 let { from, to } = bounds;
5733 let selPoints = view.docView.impreciseHead || view.docView.impreciseAnchor ? [] : selectionPoints(view);
5734 let reader = new DOMReader(selPoints, view.state);
5735 reader.readRange(bounds.startDOM, bounds.endDOM);
5736 let preferredPos = sel.from, preferredSide = null;
5737 // Prefer anchoring to end when Backspace is pressed (or, on
5738 // Android, when something was deleted)
5739 if (view.inputState.lastKeyCode === 8 && view.inputState.lastKeyTime > Date.now() - 100 ||
5740 browser.android && reader.text.length < to - from) {
5741 preferredPos = sel.to;
5742 preferredSide = "end";
5743 }
5744 let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), reader.text, preferredPos - from, preferredSide);
5745 if (diff) {
5746 // Chrome inserts two newlines when pressing shift-enter at the
5747 // end of a line. This drops one of those.
5748 if (browser.chrome && view.inputState.lastKeyCode == 13 &&
5749 diff.toB == diff.from + 2 && reader.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
5750 diff.toB--;
5751 change = { from: from + diff.from, to: from + diff.toA,
5752 insert: Text.of(reader.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
5753 }
5754 newSel = selectionFromPoints(selPoints, from);
5755 }
5756 else if (view.hasFocus || !view.state.facet(editable)) {
5757 let domSel = view.observer.selectionRange;
5758 let { impreciseHead: iHead, impreciseAnchor: iAnchor } = view.docView;
5759 let head = iHead && iHead.node == domSel.focusNode && iHead.offset == domSel.focusOffset ||
5760 !contains(view.contentDOM, domSel.focusNode)
5761 ? view.state.selection.main.head
5762 : view.docView.posFromDOM(domSel.focusNode, domSel.focusOffset);
5763 let anchor = iAnchor && iAnchor.node == domSel.anchorNode && iAnchor.offset == domSel.anchorOffset ||
5764 !contains(view.contentDOM, domSel.anchorNode)
5765 ? view.state.selection.main.anchor
5766 : view.docView.posFromDOM(domSel.anchorNode, domSel.anchorOffset);
5767 if (head != sel.head || anchor != sel.anchor)
5768 newSel = EditorSelection.single(anchor, head);
5769 }
5770 if (!change && !newSel)
5771 return false;
5772 // Heuristic to notice typing over a selected character
5773 if (!change && typeOver && !sel.empty && newSel && newSel.main.empty)
5774 change = { from: sel.from, to: sel.to, insert: view.state.doc.slice(sel.from, sel.to) };
5775 // If the change is inside the selection and covers most of it,
5776 // assume it is a selection replace (with identical characters at
5777 // the start/end not included in the diff)
5778 else if (change && change.from >= sel.from && change.to <= sel.to &&
5779 (change.from != sel.from || change.to != sel.to) &&
5780 (sel.to - sel.from) - (change.to - change.from) <= 4)
5781 change = {
5782 from: sel.from, to: sel.to,
5783 insert: view.state.doc.slice(sel.from, change.from).append(change.insert).append(view.state.doc.slice(change.to, sel.to))
5784 };
5785 // Detect insert-period-on-double-space Mac behavior, and transform
5786 // it into a regular space insert.
5787 else if ((browser.mac || browser.android) && change && change.from == change.to && change.from == sel.head - 1 &&
5788 change.insert.toString() == ".")
5789 change = { from: sel.from, to: sel.to, insert: Text.of([" "]) };
5790 if (change) {
5791 let startState = view.state;
5792 if (browser.ios && view.inputState.flushIOSKey(view))
5793 return true;
5794 // Android browsers don't fire reasonable key events for enter,
5795 // backspace, or delete. So this detects changes that look like
5796 // they're caused by those keys, and reinterprets them as key
5797 // events. (Some of these keys are also handled by beforeinput
5798 // events and the pendingAndroidKey mechanism, but that's not
5799 // reliable in all situations.)
5800 if (browser.android &&
5801 ((change.from == sel.from && change.to == sel.to &&
5802 change.insert.length == 1 && change.insert.lines == 2 &&
5803 dispatchKey(view.contentDOM, "Enter", 13)) ||
5804 (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
5805 dispatchKey(view.contentDOM, "Backspace", 8)) ||
5806 (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
5807 dispatchKey(view.contentDOM, "Delete", 46))))
5808 return true;
5809 let text = change.insert.toString();
5810 if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
5811 return true;
5812 if (view.inputState.composing >= 0)
5813 view.inputState.composing++;
5814 let tr;
5815 if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
5816 (!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
5817 view.inputState.composing < 0) {
5818 let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
5819 let after = sel.to > change.to ? startState.sliceDoc(change.to, sel.to) : "";
5820 tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
5821 }
5822 else {
5823 let changes = startState.changes(change);
5824 let mainSel = newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
5825 ? newSel.main : undefined;
5826 // Try to apply a composition change to all cursors
5827 if (startState.selection.ranges.length > 1 && view.inputState.composing >= 0 &&
5828 change.to <= sel.to && change.to >= sel.to - 10) {
5829 let replaced = view.state.sliceDoc(change.from, change.to);
5830 let compositionRange = compositionSurroundingNode(view) || view.state.doc.lineAt(sel.head);
5831 let offset = sel.to - change.to, size = sel.to - sel.from;
5832 tr = startState.changeByRange(range => {
5833 if (range.from == sel.from && range.to == sel.to)
5834 return { changes, range: mainSel || range.map(changes) };
5835 let to = range.to - offset, from = to - replaced.length;
5836 if (range.to - range.from != size || view.state.sliceDoc(from, to) != replaced ||
5837 // Unfortunately, there's no way to make multiple
5838 // changes in the same node work without aborting
5839 // composition, so cursors in the composition range are
5840 // ignored.
5841 compositionRange && range.to >= compositionRange.from && range.from <= compositionRange.to)
5842 return { range };
5843 let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
5844 return {
5845 changes: rangeChanges,
5846 range: !mainSel ? range.map(rangeChanges) :
5847 EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
5848 };
5849 });
5850 }
5851 else {
5852 tr = {
5853 changes,
5854 selection: mainSel && startState.selection.replaceRange(mainSel)
5855 };
5856 }
5857 }
5858 let userEvent = "input.type";
5859 if (view.composing) {
5860 userEvent += ".compose";
5861 if (view.inputState.compositionFirstChange) {
5862 userEvent += ".start";
5863 view.inputState.compositionFirstChange = false;
5864 }
5865 }
5866 view.dispatch(tr, { scrollIntoView: true, userEvent });
5867 return true;
5868 }
5869 else if (newSel && !newSel.main.eq(sel)) {
5870 let scrollIntoView = false, userEvent = "select";
5871 if (view.inputState.lastSelectionTime > Date.now() - 50) {
5872 if (view.inputState.lastSelectionOrigin == "select")
5873 scrollIntoView = true;
5874 userEvent = view.inputState.lastSelectionOrigin;
5875 }
5876 view.dispatch({ selection: newSel, scrollIntoView, userEvent });
5877 return true;
5878 }
5879 else {
5880 return false;
5881 }
5882}
5883function findDiff(a, b, preferredPos, preferredSide) {
5884 let minLen = Math.min(a.length, b.length);
5885 let from = 0;
5886 while (from < minLen && a.charCodeAt(from) == b.charCodeAt(from))
5887 from++;
5888 if (from == minLen && a.length == b.length)
5889 return null;
5890 let toA = a.length, toB = b.length;
5891 while (toA > 0 && toB > 0 && a.charCodeAt(toA - 1) == b.charCodeAt(toB - 1)) {
5892 toA--;
5893 toB--;
5894 }
5895 if (preferredSide == "end") {
5896 let adjust = Math.max(0, from - Math.min(toA, toB));
5897 preferredPos -= toA + adjust - from;
5898 }
5899 if (toA < from && a.length < b.length) {
5900 let move = preferredPos <= from && preferredPos >= toA ? from - preferredPos : 0;
5901 from -= move;
5902 toB = from + (toB - toA);
5903 toA = from;
5904 }
5905 else if (toB < from) {
5906 let move = preferredPos <= from && preferredPos >= toB ? from - preferredPos : 0;
5907 from -= move;
5908 toA = from + (toA - toB);
5909 toB = from;
5910 }
5911 return { from, toA, toB };
5912}
5913function selectionPoints(view) {
5914 let result = [];
5915 if (view.root.activeElement != view.contentDOM)
5916 return result;
5917 let { anchorNode, anchorOffset, focusNode, focusOffset } = view.observer.selectionRange;
5918 if (anchorNode) {
5919 result.push(new DOMPoint(anchorNode, anchorOffset));
5920 if (focusNode != anchorNode || focusOffset != anchorOffset)
5921 result.push(new DOMPoint(focusNode, focusOffset));
5922 }
5923 return result;
5924}
5925function selectionFromPoints(points, base) {
5926 if (points.length == 0)
5927 return null;
5928 let anchor = points[0].pos, head = points.length == 2 ? points[1].pos : anchor;
5929 return anchor > -1 && head > -1 ? EditorSelection.single(anchor + base, head + base) : null;
5930}
5931
5932// The editor's update state machine looks something like this:
5933//
5934// Idle → Updating ⇆ Idle (unchecked) → Measuring → Idle
5935// ↑ ↓
5936// Updating (measure)
5937//
5938// The difference between 'Idle' and 'Idle (unchecked)' lies in
5939// whether a layout check has been scheduled. A regular update through
5940// the `update` method updates the DOM in a write-only fashion, and
5941// relies on a check (scheduled with `requestAnimationFrame`) to make
5942// sure everything is where it should be and the viewport covers the
5943// visible code. That check continues to measure and then optionally
5944// update until it reaches a coherent state.
5945/**
5946An editor view represents the editor's user interface. It holds
5947the editable DOM surface, and possibly other elements such as the
5948line number gutter. It handles events and dispatches state
5949transactions for editing actions.
5950*/
5951class EditorView {
5952 /**
5953 Construct a new view. You'll want to either provide a `parent`
5954 option, or put `view.dom` into your document after creating a
5955 view, so that the user can see the editor.
5956 */
5957 constructor(config = {}) {
5958 this.plugins = [];
5959 this.pluginMap = new Map;
5960 this.editorAttrs = {};
5961 this.contentAttrs = {};
5962 this.bidiCache = [];
5963 this.destroyed = false;
5964 /**
5965 @internal
5966 */
5967 this.updateState = 2 /* Updating */;
5968 /**
5969 @internal
5970 */
5971 this.measureScheduled = -1;
5972 /**
5973 @internal
5974 */
5975 this.measureRequests = [];
5976 this.contentDOM = document.createElement("div");
5977 this.scrollDOM = document.createElement("div");
5978 this.scrollDOM.tabIndex = -1;
5979 this.scrollDOM.className = "cm-scroller";
5980 this.scrollDOM.appendChild(this.contentDOM);
5981 this.announceDOM = document.createElement("div");
5982 this.announceDOM.style.cssText = "position: absolute; top: -10000px";
5983 this.announceDOM.setAttribute("aria-live", "polite");
5984 this.dom = document.createElement("div");
5985 this.dom.appendChild(this.announceDOM);
5986 this.dom.appendChild(this.scrollDOM);
5987 this._dispatch = config.dispatch || ((tr) => this.update([tr]));
5988 this.dispatch = this.dispatch.bind(this);
5989 this.root = (config.root || getRoot(config.parent) || document);
5990 this.viewState = new ViewState(config.state || EditorState.create(config));
5991 this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec));
5992 for (let plugin of this.plugins)
5993 plugin.update(this);
5994 this.observer = new DOMObserver(this, (from, to, typeOver) => {
5995 return applyDOMChange(this, from, to, typeOver);
5996 }, event => {
5997 this.inputState.runScrollHandlers(this, event);
5998 if (this.observer.intersecting)
5999 this.measure();
6000 });
6001 this.inputState = new InputState(this);
6002 this.inputState.ensureHandlers(this, this.plugins);
6003 this.docView = new DocView(this);
6004 this.mountStyles();
6005 this.updateAttrs();
6006 this.updateState = 0 /* Idle */;
6007 this.requestMeasure();
6008 if (config.parent)
6009 config.parent.appendChild(this.dom);
6010 }
6011 /**
6012 The current editor state.
6013 */
6014 get state() { return this.viewState.state; }
6015 /**
6016 To be able to display large documents without consuming too much
6017 memory or overloading the browser, CodeMirror only draws the
6018 code that is visible (plus a margin around it) to the DOM. This
6019 property tells you the extent of the current drawn viewport, in
6020 document positions.
6021 */
6022 get viewport() { return this.viewState.viewport; }
6023 /**
6024 When there are, for example, large collapsed ranges in the
6025 viewport, its size can be a lot bigger than the actual visible
6026 content. Thus, if you are doing something like styling the
6027 content in the viewport, it is preferable to only do so for
6028 these ranges, which are the subset of the viewport that is
6029 actually drawn.
6030 */
6031 get visibleRanges() { return this.viewState.visibleRanges; }
6032 /**
6033 Returns false when the editor is entirely scrolled out of view
6034 or otherwise hidden.
6035 */
6036 get inView() { return this.viewState.inView; }
6037 /**
6038 Indicates whether the user is currently composing text via
6039 [IME](https://en.wikipedia.org/wiki/Input_method), and at least
6040 one change has been made in the current composition.
6041 */
6042 get composing() { return this.inputState.composing > 0; }
6043 /**
6044 Indicates whether the user is currently in composing state. Note
6045 that on some platforms, like Android, this will be the case a
6046 lot, since just putting the cursor on a word starts a
6047 composition there.
6048 */
6049 get compositionStarted() { return this.inputState.composing >= 0; }
6050 dispatch(...input) {
6051 this._dispatch(input.length == 1 && input[0] instanceof Transaction ? input[0]
6052 : this.state.update(...input));
6053 }
6054 /**
6055 Update the view for the given array of transactions. This will
6056 update the visible document and selection to match the state
6057 produced by the transactions, and notify view plugins of the
6058 change. You should usually call
6059 [`dispatch`](https://codemirror.net/6/docs/ref/#view.EditorView.dispatch) instead, which uses this
6060 as a primitive.
6061 */
6062 update(transactions) {
6063 if (this.updateState != 0 /* Idle */)
6064 throw new Error("Calls to EditorView.update are not allowed while an update is in progress");
6065 let redrawn = false, attrsChanged = false, update;
6066 let state = this.state;
6067 for (let tr of transactions) {
6068 if (tr.startState != state)
6069 throw new RangeError("Trying to update state with a transaction that doesn't start from the previous state.");
6070 state = tr.state;
6071 }
6072 if (this.destroyed) {
6073 this.viewState.state = state;
6074 return;
6075 }
6076 this.observer.clear();
6077 // When the phrases change, redraw the editor
6078 if (state.facet(EditorState.phrases) != this.state.facet(EditorState.phrases))
6079 return this.setState(state);
6080 update = ViewUpdate.create(this, state, transactions);
6081 let scrollTarget = this.viewState.scrollTarget;
6082 try {
6083 this.updateState = 2 /* Updating */;
6084 for (let tr of transactions) {
6085 if (scrollTarget)
6086 scrollTarget = scrollTarget.map(tr.changes);
6087 if (tr.scrollIntoView) {
6088 let { main } = tr.state.selection;
6089 scrollTarget = new ScrollTarget(main.empty ? main : EditorSelection.cursor(main.head, main.head > main.anchor ? -1 : 1));
6090 }
6091 for (let e of tr.effects)
6092 if (e.is(scrollIntoView))
6093 scrollTarget = e.value;
6094 }
6095 this.viewState.update(update, scrollTarget);
6096 this.bidiCache = CachedOrder.update(this.bidiCache, update.changes);
6097 if (!update.empty) {
6098 this.updatePlugins(update);
6099 this.inputState.update(update);
6100 }
6101 redrawn = this.docView.update(update);
6102 if (this.state.facet(styleModule) != this.styleModules)
6103 this.mountStyles();
6104 attrsChanged = this.updateAttrs();
6105 this.showAnnouncements(transactions);
6106 this.docView.updateSelection(redrawn, transactions.some(tr => tr.isUserEvent("select.pointer")));
6107 }
6108 finally {
6109 this.updateState = 0 /* Idle */;
6110 }
6111 if (update.startState.facet(theme) != update.state.facet(theme))
6112 this.viewState.mustMeasureContent = true;
6113 if (redrawn || attrsChanged || scrollTarget || this.viewState.mustEnforceCursorAssoc || this.viewState.mustMeasureContent)
6114 this.requestMeasure();
6115 if (!update.empty)
6116 for (let listener of this.state.facet(updateListener))
6117 listener(update);
6118 }
6119 /**
6120 Reset the view to the given state. (This will cause the entire
6121 document to be redrawn and all view plugins to be reinitialized,
6122 so you should probably only use it when the new state isn't
6123 derived from the old state. Otherwise, use
6124 [`dispatch`](https://codemirror.net/6/docs/ref/#view.EditorView.dispatch) instead.)
6125 */
6126 setState(newState) {
6127 if (this.updateState != 0 /* Idle */)
6128 throw new Error("Calls to EditorView.setState are not allowed while an update is in progress");
6129 if (this.destroyed) {
6130 this.viewState.state = newState;
6131 return;
6132 }
6133 this.updateState = 2 /* Updating */;
6134 let hadFocus = this.hasFocus;
6135 try {
6136 for (let plugin of this.plugins)
6137 plugin.destroy(this);
6138 this.viewState = new ViewState(newState);
6139 this.plugins = newState.facet(viewPlugin).map(spec => new PluginInstance(spec));
6140 this.pluginMap.clear();
6141 for (let plugin of this.plugins)
6142 plugin.update(this);
6143 this.docView = new DocView(this);
6144 this.inputState.ensureHandlers(this, this.plugins);
6145 this.mountStyles();
6146 this.updateAttrs();
6147 this.bidiCache = [];
6148 }
6149 finally {
6150 this.updateState = 0 /* Idle */;
6151 }
6152 if (hadFocus)
6153 this.focus();
6154 this.requestMeasure();
6155 }
6156 updatePlugins(update) {
6157 let prevSpecs = update.startState.facet(viewPlugin), specs = update.state.facet(viewPlugin);
6158 if (prevSpecs != specs) {
6159 let newPlugins = [];
6160 for (let spec of specs) {
6161 let found = prevSpecs.indexOf(spec);
6162 if (found < 0) {
6163 newPlugins.push(new PluginInstance(spec));
6164 }
6165 else {
6166 let plugin = this.plugins[found];
6167 plugin.mustUpdate = update;
6168 newPlugins.push(plugin);
6169 }
6170 }
6171 for (let plugin of this.plugins)
6172 if (plugin.mustUpdate != update)
6173 plugin.destroy(this);
6174 this.plugins = newPlugins;
6175 this.pluginMap.clear();
6176 this.inputState.ensureHandlers(this, this.plugins);
6177 }
6178 else {
6179 for (let p of this.plugins)
6180 p.mustUpdate = update;
6181 }
6182 for (let i = 0; i < this.plugins.length; i++)
6183 this.plugins[i].update(this);
6184 }
6185 /**
6186 @internal
6187 */
6188 measure(flush = true) {
6189 if (this.destroyed)
6190 return;
6191 if (this.measureScheduled > -1)
6192 cancelAnimationFrame(this.measureScheduled);
6193 this.measureScheduled = 0; // Prevent requestMeasure calls from scheduling another animation frame
6194 if (flush)
6195 this.observer.flush();
6196 let updated = null;
6197 try {
6198 for (let i = 0;; i++) {
6199 this.updateState = 1 /* Measuring */;
6200 let oldViewport = this.viewport;
6201 let changed = this.viewState.measure(this);
6202 if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
6203 break;
6204 if (i > 5) {
6205 console.warn(this.measureRequests.length
6206 ? "Measure loop restarted more than 5 times"
6207 : "Viewport failed to stabilize");
6208 break;
6209 }
6210 let measuring = [];
6211 // Only run measure requests in this cycle when the viewport didn't change
6212 if (!(changed & 4 /* Viewport */))
6213 [this.measureRequests, measuring] = [measuring, this.measureRequests];
6214 let measured = measuring.map(m => {
6215 try {
6216 return m.read(this);
6217 }
6218 catch (e) {
6219 logException(this.state, e);
6220 return BadMeasure;
6221 }
6222 });
6223 let update = ViewUpdate.create(this, this.state, []), redrawn = false, scrolled = false;
6224 update.flags |= changed;
6225 if (!updated)
6226 updated = update;
6227 else
6228 updated.flags |= changed;
6229 this.updateState = 2 /* Updating */;
6230 if (!update.empty) {
6231 this.updatePlugins(update);
6232 this.inputState.update(update);
6233 this.updateAttrs();
6234 redrawn = this.docView.update(update);
6235 }
6236 for (let i = 0; i < measuring.length; i++)
6237 if (measured[i] != BadMeasure) {
6238 try {
6239 let m = measuring[i];
6240 if (m.write)
6241 m.write(measured[i], this);
6242 }
6243 catch (e) {
6244 logException(this.state, e);
6245 }
6246 }
6247 if (this.viewState.scrollTarget) {
6248 this.docView.scrollIntoView(this.viewState.scrollTarget);
6249 this.viewState.scrollTarget = null;
6250 scrolled = true;
6251 }
6252 if (redrawn)
6253 this.docView.updateSelection(true);
6254 if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
6255 !scrolled && this.measureRequests.length == 0)
6256 break;
6257 }
6258 }
6259 finally {
6260 this.updateState = 0 /* Idle */;
6261 this.measureScheduled = -1;
6262 }
6263 if (updated && !updated.empty)
6264 for (let listener of this.state.facet(updateListener))
6265 listener(updated);
6266 }
6267 /**
6268 Get the CSS classes for the currently active editor themes.
6269 */
6270 get themeClasses() {
6271 return baseThemeID + " " +
6272 (this.state.facet(darkTheme) ? baseDarkID : baseLightID) + " " +
6273 this.state.facet(theme);
6274 }
6275 updateAttrs() {
6276 let editorAttrs = attrsFromFacet(this, editorAttributes, {
6277 class: "cm-editor" + (this.hasFocus ? " cm-focused " : " ") + this.themeClasses
6278 });
6279 let contentAttrs = {
6280 spellcheck: "false",
6281 autocorrect: "off",
6282 autocapitalize: "off",
6283 translate: "no",
6284 contenteditable: !this.state.facet(editable) ? "false" : "true",
6285 class: "cm-content",
6286 style: `${browser.tabSize}: ${this.state.tabSize}`,
6287 role: "textbox",
6288 "aria-multiline": "true"
6289 };
6290 if (this.state.readOnly)
6291 contentAttrs["aria-readonly"] = "true";
6292 attrsFromFacet(this, contentAttributes, contentAttrs);
6293 let changed = this.observer.ignore(() => {
6294 let changedContent = updateAttrs(this.contentDOM, this.contentAttrs, contentAttrs);
6295 let changedEditor = updateAttrs(this.dom, this.editorAttrs, editorAttrs);
6296 return changedContent || changedEditor;
6297 });
6298 this.editorAttrs = editorAttrs;
6299 this.contentAttrs = contentAttrs;
6300 return changed;
6301 }
6302 showAnnouncements(trs) {
6303 let first = true;
6304 for (let tr of trs)
6305 for (let effect of tr.effects)
6306 if (effect.is(EditorView.announce)) {
6307 if (first)
6308 this.announceDOM.textContent = "";
6309 first = false;
6310 let div = this.announceDOM.appendChild(document.createElement("div"));
6311 div.textContent = effect.value;
6312 }
6313 }
6314 mountStyles() {
6315 this.styleModules = this.state.facet(styleModule);
6316 StyleModule.mount(this.root, this.styleModules.concat(baseTheme$1).reverse());
6317 }
6318 readMeasured() {
6319 if (this.updateState == 2 /* Updating */)
6320 throw new Error("Reading the editor layout isn't allowed during an update");
6321 if (this.updateState == 0 /* Idle */ && this.measureScheduled > -1)
6322 this.measure(false);
6323 }
6324 /**
6325 Schedule a layout measurement, optionally providing callbacks to
6326 do custom DOM measuring followed by a DOM write phase. Using
6327 this is preferable reading DOM layout directly from, for
6328 example, an event handler, because it'll make sure measuring and
6329 drawing done by other components is synchronized, avoiding
6330 unnecessary DOM layout computations.
6331 */
6332 requestMeasure(request) {
6333 if (this.measureScheduled < 0)
6334 this.measureScheduled = requestAnimationFrame(() => this.measure());
6335 if (request) {
6336 if (request.key != null)
6337 for (let i = 0; i < this.measureRequests.length; i++) {
6338 if (this.measureRequests[i].key === request.key) {
6339 this.measureRequests[i] = request;
6340 return;
6341 }
6342 }
6343 this.measureRequests.push(request);
6344 }
6345 }
6346 /**
6347 Get the value of a specific plugin, if present. Note that
6348 plugins that crash can be dropped from a view, so even when you
6349 know you registered a given plugin, it is recommended to check
6350 the return value of this method.
6351 */
6352 plugin(plugin) {
6353 let known = this.pluginMap.get(plugin);
6354 if (known === undefined || known && known.spec != plugin)
6355 this.pluginMap.set(plugin, known = this.plugins.find(p => p.spec == plugin) || null);
6356 return known && known.update(this).value;
6357 }
6358 /**
6359 The top position of the document, in screen coordinates. This
6360 may be negative when the editor is scrolled down. Points
6361 directly to the top of the first line, not above the padding.
6362 */
6363 get documentTop() {
6364 return this.contentDOM.getBoundingClientRect().top + this.viewState.paddingTop;
6365 }
6366 /**
6367 Reports the padding above and below the document.
6368 */
6369 get documentPadding() {
6370 return { top: this.viewState.paddingTop, bottom: this.viewState.paddingBottom };
6371 }
6372 /**
6373 Find the text line or block widget at the given vertical
6374 position (which is interpreted as relative to the [top of the
6375 document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop)
6376 */
6377 elementAtHeight(height) {
6378 this.readMeasured();
6379 return this.viewState.elementAtHeight(height);
6380 }
6381 /**
6382 Find the line block (see
6383 [`lineBlockAt`](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt) at the given
6384 height.
6385 */
6386 lineBlockAtHeight(height) {
6387 this.readMeasured();
6388 return this.viewState.lineBlockAtHeight(height);
6389 }
6390 /**
6391 Get the extent and vertical position of all [line
6392 blocks](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt) in the viewport. Positions
6393 are relative to the [top of the
6394 document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop);
6395 */
6396 get viewportLineBlocks() {
6397 return this.viewState.viewportLines;
6398 }
6399 /**
6400 Find the line block around the given document position. A line
6401 block is a range delimited on both sides by either a
6402 non-[hidden](https://codemirror.net/6/docs/ref/#view.Decoration^replace) line breaks, or the
6403 start/end of the document. It will usually just hold a line of
6404 text, but may be broken into multiple textblocks by block
6405 widgets.
6406 */
6407 lineBlockAt(pos) {
6408 return this.viewState.lineBlockAt(pos);
6409 }
6410 /**
6411 The editor's total content height.
6412 */
6413 get contentHeight() {
6414 return this.viewState.contentHeight;
6415 }
6416 /**
6417 Move a cursor position by [grapheme
6418 cluster](https://codemirror.net/6/docs/ref/#state.findClusterBreak). `forward` determines whether
6419 the motion is away from the line start, or towards it. In
6420 bidirectional text, the line is traversed in visual order, using
6421 the editor's [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
6422 When the start position was the last one on the line, the
6423 returned position will be across the line break. If there is no
6424 further line, the original position is returned.
6425
6426 By default, this method moves over a single cluster. The
6427 optional `by` argument can be used to move across more. It will
6428 be called with the first cluster as argument, and should return
6429 a predicate that determines, for each subsequent cluster,
6430 whether it should also be moved over.
6431 */
6432 moveByChar(start, forward, by) {
6433 return skipAtoms(this, start, moveByChar(this, start, forward, by));
6434 }
6435 /**
6436 Move a cursor position across the next group of either
6437 [letters](https://codemirror.net/6/docs/ref/#state.EditorState.charCategorizer) or non-letter
6438 non-whitespace characters.
6439 */
6440 moveByGroup(start, forward) {
6441 return skipAtoms(this, start, moveByChar(this, start, forward, initial => byGroup(this, start.head, initial)));
6442 }
6443 /**
6444 Move to the next line boundary in the given direction. If
6445 `includeWrap` is true, line wrapping is on, and there is a
6446 further wrap point on the current line, the wrap point will be
6447 returned. Otherwise this function will return the start or end
6448 of the line.
6449 */
6450 moveToLineBoundary(start, forward, includeWrap = true) {
6451 return moveToLineBoundary(this, start, forward, includeWrap);
6452 }
6453 /**
6454 Move a cursor position vertically. When `distance` isn't given,
6455 it defaults to moving to the next line (including wrapped
6456 lines). Otherwise, `distance` should provide a positive distance
6457 in pixels.
6458
6459 When `start` has a
6460 [`goalColumn`](https://codemirror.net/6/docs/ref/#state.SelectionRange.goalColumn), the vertical
6461 motion will use that as a target horizontal position. Otherwise,
6462 the cursor's own horizontal position is used. The returned
6463 cursor will have its goal column set to whichever column was
6464 used.
6465 */
6466 moveVertically(start, forward, distance) {
6467 return skipAtoms(this, start, moveVertically(this, start, forward, distance));
6468 }
6469 /**
6470 Find the DOM parent node and offset (child offset if `node` is
6471 an element, character offset when it is a text node) at the
6472 given document position.
6473
6474 Note that for positions that aren't currently in
6475 `visibleRanges`, the resulting DOM position isn't necessarily
6476 meaningful (it may just point before or after a placeholder
6477 element).
6478 */
6479 domAtPos(pos) {
6480 return this.docView.domAtPos(pos);
6481 }
6482 /**
6483 Find the document position at the given DOM node. Can be useful
6484 for associating positions with DOM events. Will raise an error
6485 when `node` isn't part of the editor content.
6486 */
6487 posAtDOM(node, offset = 0) {
6488 return this.docView.posFromDOM(node, offset);
6489 }
6490 posAtCoords(coords, precise = true) {
6491 this.readMeasured();
6492 return posAtCoords(this, coords, precise);
6493 }
6494 /**
6495 Get the screen coordinates at the given document position.
6496 `side` determines whether the coordinates are based on the
6497 element before (-1) or after (1) the position (if no element is
6498 available on the given side, the method will transparently use
6499 another strategy to get reasonable coordinates).
6500 */
6501 coordsAtPos(pos, side = 1) {
6502 this.readMeasured();
6503 let rect = this.docView.coordsAt(pos, side);
6504 if (!rect || rect.left == rect.right)
6505 return rect;
6506 let line = this.state.doc.lineAt(pos), order = this.bidiSpans(line);
6507 let span = order[BidiSpan.find(order, pos - line.from, -1, side)];
6508 return flattenRect(rect, (span.dir == Direction.LTR) == (side > 0));
6509 }
6510 /**
6511 The default width of a character in the editor. May not
6512 accurately reflect the width of all characters (given variable
6513 width fonts or styling of invididual ranges).
6514 */
6515 get defaultCharacterWidth() { return this.viewState.heightOracle.charWidth; }
6516 /**
6517 The default height of a line in the editor. May not be accurate
6518 for all lines.
6519 */
6520 get defaultLineHeight() { return this.viewState.heightOracle.lineHeight; }
6521 /**
6522 The text direction
6523 ([`direction`](https://developer.mozilla.org/en-US/docs/Web/CSS/direction)
6524 CSS property) of the editor's content element.
6525 */
6526 get textDirection() { return this.viewState.defaultTextDirection; }
6527 /**
6528 Find the text direction of the block at the given position, as
6529 assigned by CSS. If
6530 [`perLineTextDirection`](https://codemirror.net/6/docs/ref/#view.EditorView^perLineTextDirection)
6531 isn't enabled, or the given position is outside of the viewport,
6532 this will always return the same as
6533 [`textDirection`](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection). Note that
6534 this may trigger a DOM layout.
6535 */
6536 textDirectionAt(pos) {
6537 let perLine = this.state.facet(perLineTextDirection);
6538 if (!perLine || pos < this.viewport.from || pos > this.viewport.to)
6539 return this.textDirection;
6540 this.readMeasured();
6541 return this.docView.textDirectionAt(pos);
6542 }
6543 /**
6544 Whether this editor [wraps lines](https://codemirror.net/6/docs/ref/#view.EditorView.lineWrapping)
6545 (as determined by the
6546 [`white-space`](https://developer.mozilla.org/en-US/docs/Web/CSS/white-space)
6547 CSS property of its content element).
6548 */
6549 get lineWrapping() { return this.viewState.heightOracle.lineWrapping; }
6550 /**
6551 Returns the bidirectional text structure of the given line
6552 (which should be in the current document) as an array of span
6553 objects. The order of these spans matches the [text
6554 direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection)—if that is
6555 left-to-right, the leftmost spans come first, otherwise the
6556 rightmost spans come first.
6557 */
6558 bidiSpans(line) {
6559 if (line.length > MaxBidiLine)
6560 return trivialOrder(line.length);
6561 let dir = this.textDirectionAt(line.from);
6562 for (let entry of this.bidiCache)
6563 if (entry.from == line.from && entry.dir == dir)
6564 return entry.order;
6565 let order = computeOrder(line.text, dir);
6566 this.bidiCache.push(new CachedOrder(line.from, line.to, dir, order));
6567 return order;
6568 }
6569 /**
6570 Check whether the editor has focus.
6571 */
6572 get hasFocus() {
6573 var _a;
6574 // Safari return false for hasFocus when the context menu is open
6575 // or closing, which leads us to ignore selection changes from the
6576 // context menu because it looks like the editor isn't focused.
6577 // This kludges around that.
6578 return (document.hasFocus() || browser.safari && ((_a = this.inputState) === null || _a === void 0 ? void 0 : _a.lastContextMenu) > Date.now() - 3e4) &&
6579 this.root.activeElement == this.contentDOM;
6580 }
6581 /**
6582 Put focus on the editor.
6583 */
6584 focus() {
6585 this.observer.ignore(() => {
6586 focusPreventScroll(this.contentDOM);
6587 this.docView.updateSelection();
6588 });
6589 }
6590 /**
6591 Clean up this editor view, removing its element from the
6592 document, unregistering event handlers, and notifying
6593 plugins. The view instance can no longer be used after
6594 calling this.
6595 */
6596 destroy() {
6597 for (let plugin of this.plugins)
6598 plugin.destroy(this);
6599 this.plugins = [];
6600 this.inputState.destroy();
6601 this.dom.remove();
6602 this.observer.destroy();
6603 if (this.measureScheduled > -1)
6604 cancelAnimationFrame(this.measureScheduled);
6605 this.destroyed = true;
6606 }
6607 /**
6608 Returns an effect that can be
6609 [added](https://codemirror.net/6/docs/ref/#state.TransactionSpec.effects) to a transaction to
6610 cause it to scroll the given position or range into view.
6611 */
6612 static scrollIntoView(pos, options = {}) {
6613 return scrollIntoView.of(new ScrollTarget(typeof pos == "number" ? EditorSelection.cursor(pos) : pos, options.y, options.x, options.yMargin, options.xMargin));
6614 }
6615 /**
6616 Returns an extension that can be used to add DOM event handlers.
6617 The value should be an object mapping event names to handler
6618 functions. For any given event, such functions are ordered by
6619 extension precedence, and the first handler to return true will
6620 be assumed to have handled that event, and no other handlers or
6621 built-in behavior will be activated for it. These are registered
6622 on the [content element](https://codemirror.net/6/docs/ref/#view.EditorView.contentDOM), except
6623 for `scroll` handlers, which will be called any time the
6624 editor's [scroll element](https://codemirror.net/6/docs/ref/#view.EditorView.scrollDOM) or one of
6625 its parent nodes is scrolled.
6626 */
6627 static domEventHandlers(handlers) {
6628 return ViewPlugin.define(() => ({}), { eventHandlers: handlers });
6629 }
6630 /**
6631 Create a theme extension. The first argument can be a
6632 [`style-mod`](https://github.com/marijnh/style-mod#documentation)
6633 style spec providing the styles for the theme. These will be
6634 prefixed with a generated class for the style.
6635
6636 Because the selectors will be prefixed with a scope class, rule
6637 that directly match the editor's [wrapper
6638 element](https://codemirror.net/6/docs/ref/#view.EditorView.dom)—to which the scope class will be
6639 added—need to be explicitly differentiated by adding an `&` to
6640 the selector for that element—for example
6641 `&.cm-focused`.
6642
6643 When `dark` is set to true, the theme will be marked as dark,
6644 which will cause the `&dark` rules from [base
6645 themes](https://codemirror.net/6/docs/ref/#view.EditorView^baseTheme) to be used (as opposed to
6646 `&light` when a light theme is active).
6647 */
6648 static theme(spec, options) {
6649 let prefix = StyleModule.newName();
6650 let result = [theme.of(prefix), styleModule.of(buildTheme(`.${prefix}`, spec))];
6651 if (options && options.dark)
6652 result.push(darkTheme.of(true));
6653 return result;
6654 }
6655 /**
6656 Create an extension that adds styles to the base theme. Like
6657 with [`theme`](https://codemirror.net/6/docs/ref/#view.EditorView^theme), use `&` to indicate the
6658 place of the editor wrapper element when directly targeting
6659 that. You can also use `&dark` or `&light` instead to only
6660 target editors with a dark or light theme.
6661 */
6662 static baseTheme(spec) {
6663 return Prec.lowest(styleModule.of(buildTheme("." + baseThemeID, spec, lightDarkIDs)));
6664 }
6665 /**
6666 Retrieve an editor view instance from the view's DOM
6667 representation.
6668 */
6669 static findFromDOM(dom) {
6670 var _a;
6671 let content = dom.querySelector(".cm-content");
6672 let cView = content && ContentView.get(content) || ContentView.get(dom);
6673 return ((_a = cView === null || cView === void 0 ? void 0 : cView.rootView) === null || _a === void 0 ? void 0 : _a.view) || null;
6674 }
6675}
6676/**
6677Facet to add a [style
6678module](https://github.com/marijnh/style-mod#documentation) to
6679an editor view. The view will ensure that the module is
6680mounted in its [document
6681root](https://codemirror.net/6/docs/ref/#view.EditorView.constructor^config.root).
6682*/
6683EditorView.styleModule = styleModule;
6684/**
6685An input handler can override the way changes to the editable
6686DOM content are handled. Handlers are passed the document
6687positions between which the change was found, and the new
6688content. When one returns true, no further input handlers are
6689called and the default behavior is prevented.
6690*/
6691EditorView.inputHandler = inputHandler;
6692/**
6693By default, the editor assumes all its content has the same
6694[text direction](https://codemirror.net/6/docs/ref/#view.Direction). Configure this with a `true`
6695value to make it read the text direction of every (rendered)
6696line separately.
6697*/
6698EditorView.perLineTextDirection = perLineTextDirection;
6699/**
6700Allows you to provide a function that should be called when the
6701library catches an exception from an extension (mostly from view
6702plugins, but may be used by other extensions to route exceptions
6703from user-code-provided callbacks). This is mostly useful for
6704debugging and logging. See [`logException`](https://codemirror.net/6/docs/ref/#view.logException).
6705*/
6706EditorView.exceptionSink = exceptionSink;
6707/**
6708A facet that can be used to register a function to be called
6709every time the view updates.
6710*/
6711EditorView.updateListener = updateListener;
6712/**
6713Facet that controls whether the editor content DOM is editable.
6714When its highest-precedence value is `false`, the element will
6715not have its `contenteditable` attribute set. (Note that this
6716doesn't affect API calls that change the editor content, even
6717when those are bound to keys or buttons. See the
6718[`readOnly`](https://codemirror.net/6/docs/ref/#state.EditorState.readOnly) facet for that.)
6719*/
6720EditorView.editable = editable;
6721/**
6722Allows you to influence the way mouse selection happens. The
6723functions in this facet will be called for a `mousedown` event
6724on the editor, and can return an object that overrides the way a
6725selection is computed from that mouse click or drag.
6726*/
6727EditorView.mouseSelectionStyle = mouseSelectionStyle;
6728/**
6729Facet used to configure whether a given selection drag event
6730should move or copy the selection. The given predicate will be
6731called with the `mousedown` event, and can return `true` when
6732the drag should move the content.
6733*/
6734EditorView.dragMovesSelection = dragMovesSelection$1;
6735/**
6736Facet used to configure whether a given selecting click adds a
6737new range to the existing selection or replaces it entirely. The
6738default behavior is to check `event.metaKey` on macOS, and
6739`event.ctrlKey` elsewhere.
6740*/
6741EditorView.clickAddsSelectionRange = clickAddsSelectionRange;
6742/**
6743A facet that determines which [decorations](https://codemirror.net/6/docs/ref/#view.Decoration)
6744are shown in the view. Decorations can be provided in two
6745ways—directly, or via a function that takes an editor view.
6746
6747Only decoration sets provided directly are allowed to influence
6748the editor's vertical layout structure. The ones provided as
6749functions are called _after_ the new viewport has been computed,
6750and thus **must not** introduce block widgets or replacing
6751decorations that cover line breaks.
6752*/
6753EditorView.decorations = decorations;
6754/**
6755Used to provide ranges that should be treated as atoms as far as
6756cursor motion is concerned. This causes methods like
6757[`moveByChar`](https://codemirror.net/6/docs/ref/#view.EditorView.moveByChar) and
6758[`moveVertically`](https://codemirror.net/6/docs/ref/#view.EditorView.moveVertically) (and the
6759commands built on top of them) to skip across such regions when
6760a selection endpoint would enter them. This does _not_ prevent
6761direct programmatic [selection
6762updates](https://codemirror.net/6/docs/ref/#state.TransactionSpec.selection) from moving into such
6763regions.
6764*/
6765EditorView.atomicRanges = atomicRanges;
6766/**
6767Facet that allows extensions to provide additional scroll
6768margins (space around the sides of the scrolling element that
6769should be considered invisible). This can be useful when the
6770plugin introduces elements that cover part of that element (for
6771example a horizontally fixed gutter).
6772*/
6773EditorView.scrollMargins = scrollMargins;
6774/**
6775This facet records whether a dark theme is active. The extension
6776returned by [`theme`](https://codemirror.net/6/docs/ref/#view.EditorView^theme) automatically
6777includes an instance of this when the `dark` option is set to
6778true.
6779*/
6780EditorView.darkTheme = darkTheme;
6781/**
6782Facet that provides additional DOM attributes for the editor's
6783editable DOM element.
6784*/
6785EditorView.contentAttributes = contentAttributes;
6786/**
6787Facet that provides DOM attributes for the editor's outer
6788element.
6789*/
6790EditorView.editorAttributes = editorAttributes;
6791/**
6792An extension that enables line wrapping in the editor (by
6793setting CSS `white-space` to `pre-wrap` in the content).
6794*/
6795EditorView.lineWrapping = /*@__PURE__*/EditorView.contentAttributes.of({ "class": "cm-lineWrapping" });
6796/**
6797State effect used to include screen reader announcements in a
6798transaction. These will be added to the DOM in a visually hidden
6799element with `aria-live="polite"` set, and should be used to
6800describe effects that are visually obvious but may not be
6801noticed by screen reader users (such as moving to the next
6802search match).
6803*/
6804EditorView.announce = /*@__PURE__*/StateEffect.define();
6805// Maximum line length for which we compute accurate bidi info
6806const MaxBidiLine = 4096;
6807const BadMeasure = {};
6808class CachedOrder {
6809 constructor(from, to, dir, order) {
6810 this.from = from;
6811 this.to = to;
6812 this.dir = dir;
6813 this.order = order;
6814 }
6815 static update(cache, changes) {
6816 if (changes.empty)
6817 return cache;
6818 let result = [], lastDir = cache.length ? cache[cache.length - 1].dir : Direction.LTR;
6819 for (let i = Math.max(0, cache.length - 10); i < cache.length; i++) {
6820 let entry = cache[i];
6821 if (entry.dir == lastDir && !changes.touchesRange(entry.from, entry.to))
6822 result.push(new CachedOrder(changes.mapPos(entry.from, 1), changes.mapPos(entry.to, -1), entry.dir, entry.order));
6823 }
6824 return result;
6825 }
6826}
6827function attrsFromFacet(view, facet, base) {
6828 for (let sources = view.state.facet(facet), i = sources.length - 1; i >= 0; i--) {
6829 let source = sources[i], value = typeof source == "function" ? source(view) : source;
6830 if (value)
6831 combineAttrs(value, base);
6832 }
6833 return base;
6834}
6835
6836const currentPlatform = browser.mac ? "mac" : browser.windows ? "win" : browser.linux ? "linux" : "key";
6837function normalizeKeyName(name, platform) {
6838 const parts = name.split(/-(?!$)/);
6839 let result = parts[parts.length - 1];
6840 if (result == "Space")
6841 result = " ";
6842 let alt, ctrl, shift, meta;
6843 for (let i = 0; i < parts.length - 1; ++i) {
6844 const mod = parts[i];
6845 if (/^(cmd|meta|m)$/i.test(mod))
6846 meta = true;
6847 else if (/^a(lt)?$/i.test(mod))
6848 alt = true;
6849 else if (/^(c|ctrl|control)$/i.test(mod))
6850 ctrl = true;
6851 else if (/^s(hift)?$/i.test(mod))
6852 shift = true;
6853 else if (/^mod$/i.test(mod)) {
6854 if (platform == "mac")
6855 meta = true;
6856 else
6857 ctrl = true;
6858 }
6859 else
6860 throw new Error("Unrecognized modifier name: " + mod);
6861 }
6862 if (alt)
6863 result = "Alt-" + result;
6864 if (ctrl)
6865 result = "Ctrl-" + result;
6866 if (meta)
6867 result = "Meta-" + result;
6868 if (shift)
6869 result = "Shift-" + result;
6870 return result;
6871}
6872function modifiers(name, event, shift) {
6873 if (event.altKey)
6874 name = "Alt-" + name;
6875 if (event.ctrlKey)
6876 name = "Ctrl-" + name;
6877 if (event.metaKey)
6878 name = "Meta-" + name;
6879 if (shift !== false && event.shiftKey)
6880 name = "Shift-" + name;
6881 return name;
6882}
6883const handleKeyEvents = /*@__PURE__*/EditorView.domEventHandlers({
6884 keydown(event, view) {
6885 return runHandlers(getKeymap(view.state), event, view, "editor");
6886 }
6887});
6888/**
6889Facet used for registering keymaps.
6890
6891You can add multiple keymaps to an editor. Their priorities
6892determine their precedence (the ones specified early or with high
6893priority get checked first). When a handler has returned `true`
6894for a given key, no further handlers are called.
6895*/
6896const keymap = /*@__PURE__*/Facet.define({ enables: handleKeyEvents });
6897const Keymaps = /*@__PURE__*/new WeakMap();
6898// This is hidden behind an indirection, rather than directly computed
6899// by the facet, to keep internal types out of the facet's type.
6900function getKeymap(state) {
6901 let bindings = state.facet(keymap);
6902 let map = Keymaps.get(bindings);
6903 if (!map)
6904 Keymaps.set(bindings, map = buildKeymap(bindings.reduce((a, b) => a.concat(b), [])));
6905 return map;
6906}
6907/**
6908Run the key handlers registered for a given scope. The event
6909object should be a `"keydown"` event. Returns true if any of the
6910handlers handled it.
6911*/
6912function runScopeHandlers(view, event, scope) {
6913 return runHandlers(getKeymap(view.state), event, view, scope);
6914}
6915let storedPrefix = null;
6916const PrefixTimeout = 4000;
6917function buildKeymap(bindings, platform = currentPlatform) {
6918 let bound = Object.create(null);
6919 let isPrefix = Object.create(null);
6920 let checkPrefix = (name, is) => {
6921 let current = isPrefix[name];
6922 if (current == null)
6923 isPrefix[name] = is;
6924 else if (current != is)
6925 throw new Error("Key binding " + name + " is used both as a regular binding and as a multi-stroke prefix");
6926 };
6927 let add = (scope, key, command, preventDefault) => {
6928 let scopeObj = bound[scope] || (bound[scope] = Object.create(null));
6929 let parts = key.split(/ (?!$)/).map(k => normalizeKeyName(k, platform));
6930 for (let i = 1; i < parts.length; i++) {
6931 let prefix = parts.slice(0, i).join(" ");
6932 checkPrefix(prefix, true);
6933 if (!scopeObj[prefix])
6934 scopeObj[prefix] = {
6935 preventDefault: true,
6936 commands: [(view) => {
6937 let ourObj = storedPrefix = { view, prefix, scope };
6938 setTimeout(() => { if (storedPrefix == ourObj)
6939 storedPrefix = null; }, PrefixTimeout);
6940 return true;
6941 }]
6942 };
6943 }
6944 let full = parts.join(" ");
6945 checkPrefix(full, false);
6946 let binding = scopeObj[full] || (scopeObj[full] = { preventDefault: false, commands: [] });
6947 binding.commands.push(command);
6948 if (preventDefault)
6949 binding.preventDefault = true;
6950 };
6951 for (let b of bindings) {
6952 let name = b[platform] || b.key;
6953 if (!name)
6954 continue;
6955 for (let scope of b.scope ? b.scope.split(" ") : ["editor"]) {
6956 add(scope, name, b.run, b.preventDefault);
6957 if (b.shift)
6958 add(scope, "Shift-" + name, b.shift, b.preventDefault);
6959 }
6960 }
6961 return bound;
6962}
6963function runHandlers(map, event, view, scope) {
6964 let name = keyName(event);
6965 let charCode = codePointAt(name, 0), isChar = codePointSize(charCode) == name.length && name != " ";
6966 let prefix = "", fallthrough = false;
6967 if (storedPrefix && storedPrefix.view == view && storedPrefix.scope == scope) {
6968 prefix = storedPrefix.prefix + " ";
6969 if (fallthrough = modifierCodes.indexOf(event.keyCode) < 0)
6970 storedPrefix = null;
6971 }
6972 let runFor = (binding) => {
6973 if (binding) {
6974 for (let cmd of binding.commands)
6975 if (cmd(view))
6976 return true;
6977 if (binding.preventDefault)
6978 fallthrough = true;
6979 }
6980 return false;
6981 };
6982 let scopeObj = map[scope], baseName;
6983 if (scopeObj) {
6984 if (runFor(scopeObj[prefix + modifiers(name, event, !isChar)]))
6985 return true;
6986 if (isChar && (event.shiftKey || event.altKey || event.metaKey || charCode > 127) &&
6987 (baseName = base[event.keyCode]) && baseName != name) {
6988 if (runFor(scopeObj[prefix + modifiers(baseName, event, true)]))
6989 return true;
6990 else if (event.shiftKey && shift[event.keyCode] != baseName &&
6991 runFor(scopeObj[prefix + modifiers(shift[event.keyCode], event, false)]))
6992 return true;
6993 }
6994 else if (isChar && event.shiftKey) {
6995 if (runFor(scopeObj[prefix + modifiers(name, event, true)]))
6996 return true;
6997 }
6998 }
6999 return fallthrough;
7000}
7001
7002const CanHidePrimary = !browser.ios; // FIXME test IE
7003const selectionConfig = /*@__PURE__*/Facet.define({
7004 combine(configs) {
7005 return combineConfig(configs, {
7006 cursorBlinkRate: 1200,
7007 drawRangeCursor: true
7008 }, {
7009 cursorBlinkRate: (a, b) => Math.min(a, b),
7010 drawRangeCursor: (a, b) => a || b
7011 });
7012 }
7013});
7014/**
7015Returns an extension that hides the browser's native selection and
7016cursor, replacing the selection with a background behind the text
7017(with the `cm-selectionBackground` class), and the
7018cursors with elements overlaid over the code (using
7019`cm-cursor-primary` and `cm-cursor-secondary`).
7020
7021This allows the editor to display secondary selection ranges, and
7022tends to produce a type of selection more in line with that users
7023expect in a text editor (the native selection styling will often
7024leave gaps between lines and won't fill the horizontal space after
7025a line when the selection continues past it).
7026
7027It does have a performance cost, in that it requires an extra DOM
7028layout cycle for many updates (the selection is drawn based on DOM
7029layout information that's only available after laying out the
7030content).
7031*/
7032function drawSelection(config = {}) {
7033 return [
7034 selectionConfig.of(config),
7035 drawSelectionPlugin,
7036 hideNativeSelection
7037 ];
7038}
7039class Piece {
7040 constructor(left, top, width, height, className) {
7041 this.left = left;
7042 this.top = top;
7043 this.width = width;
7044 this.height = height;
7045 this.className = className;
7046 }
7047 draw() {
7048 let elt = document.createElement("div");
7049 elt.className = this.className;
7050 this.adjust(elt);
7051 return elt;
7052 }
7053 adjust(elt) {
7054 elt.style.left = this.left + "px";
7055 elt.style.top = this.top + "px";
7056 if (this.width >= 0)
7057 elt.style.width = this.width + "px";
7058 elt.style.height = this.height + "px";
7059 }
7060 eq(p) {
7061 return this.left == p.left && this.top == p.top && this.width == p.width && this.height == p.height &&
7062 this.className == p.className;
7063 }
7064}
7065const drawSelectionPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
7066 constructor(view) {
7067 this.view = view;
7068 this.rangePieces = [];
7069 this.cursors = [];
7070 this.measureReq = { read: this.readPos.bind(this), write: this.drawSel.bind(this) };
7071 this.selectionLayer = view.scrollDOM.appendChild(document.createElement("div"));
7072 this.selectionLayer.className = "cm-selectionLayer";
7073 this.selectionLayer.setAttribute("aria-hidden", "true");
7074 this.cursorLayer = view.scrollDOM.appendChild(document.createElement("div"));
7075 this.cursorLayer.className = "cm-cursorLayer";
7076 this.cursorLayer.setAttribute("aria-hidden", "true");
7077 view.requestMeasure(this.measureReq);
7078 this.setBlinkRate();
7079 }
7080 setBlinkRate() {
7081 this.cursorLayer.style.animationDuration = this.view.state.facet(selectionConfig).cursorBlinkRate + "ms";
7082 }
7083 update(update) {
7084 let confChanged = update.startState.facet(selectionConfig) != update.state.facet(selectionConfig);
7085 if (confChanged || update.selectionSet || update.geometryChanged || update.viewportChanged)
7086 this.view.requestMeasure(this.measureReq);
7087 if (update.transactions.some(tr => tr.scrollIntoView))
7088 this.cursorLayer.style.animationName = this.cursorLayer.style.animationName == "cm-blink" ? "cm-blink2" : "cm-blink";
7089 if (confChanged)
7090 this.setBlinkRate();
7091 }
7092 readPos() {
7093 let { state } = this.view, conf = state.facet(selectionConfig);
7094 let rangePieces = state.selection.ranges.map(r => r.empty ? [] : measureRange(this.view, r)).reduce((a, b) => a.concat(b));
7095 let cursors = [];
7096 for (let r of state.selection.ranges) {
7097 let prim = r == state.selection.main;
7098 if (r.empty ? !prim || CanHidePrimary : conf.drawRangeCursor) {
7099 let piece = measureCursor(this.view, r, prim);
7100 if (piece)
7101 cursors.push(piece);
7102 }
7103 }
7104 return { rangePieces, cursors };
7105 }
7106 drawSel({ rangePieces, cursors }) {
7107 if (rangePieces.length != this.rangePieces.length || rangePieces.some((p, i) => !p.eq(this.rangePieces[i]))) {
7108 this.selectionLayer.textContent = "";
7109 for (let p of rangePieces)
7110 this.selectionLayer.appendChild(p.draw());
7111 this.rangePieces = rangePieces;
7112 }
7113 if (cursors.length != this.cursors.length || cursors.some((c, i) => !c.eq(this.cursors[i]))) {
7114 let oldCursors = this.cursorLayer.children;
7115 if (oldCursors.length !== cursors.length) {
7116 this.cursorLayer.textContent = "";
7117 for (const c of cursors)
7118 this.cursorLayer.appendChild(c.draw());
7119 }
7120 else {
7121 cursors.forEach((c, idx) => c.adjust(oldCursors[idx]));
7122 }
7123 this.cursors = cursors;
7124 }
7125 }
7126 destroy() {
7127 this.selectionLayer.remove();
7128 this.cursorLayer.remove();
7129 }
7130});
7131const themeSpec = {
7132 ".cm-line": {
7133 "& ::selection": { backgroundColor: "transparent !important" },
7134 "&::selection": { backgroundColor: "transparent !important" }
7135 }
7136};
7137if (CanHidePrimary)
7138 themeSpec[".cm-line"].caretColor = "transparent !important";
7139const hideNativeSelection = /*@__PURE__*/Prec.highest(/*@__PURE__*/EditorView.theme(themeSpec));
7140function getBase(view) {
7141 let rect = view.scrollDOM.getBoundingClientRect();
7142 let left = view.textDirection == Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth;
7143 return { left: left - view.scrollDOM.scrollLeft, top: rect.top - view.scrollDOM.scrollTop };
7144}
7145function wrappedLine(view, pos, inside) {
7146 let range = EditorSelection.cursor(pos);
7147 return { from: Math.max(inside.from, view.moveToLineBoundary(range, false, true).from),
7148 to: Math.min(inside.to, view.moveToLineBoundary(range, true, true).from),
7149 type: BlockType.Text };
7150}
7151function blockAt(view, pos) {
7152 let line = view.lineBlockAt(pos);
7153 if (Array.isArray(line.type))
7154 for (let l of line.type) {
7155 if (l.to > pos || l.to == pos && (l.to == line.to || l.type == BlockType.Text))
7156 return l;
7157 }
7158 return line;
7159}
7160function measureRange(view, range) {
7161 if (range.to <= view.viewport.from || range.from >= view.viewport.to)
7162 return [];
7163 let from = Math.max(range.from, view.viewport.from), to = Math.min(range.to, view.viewport.to);
7164 let ltr = view.textDirection == Direction.LTR;
7165 let content = view.contentDOM, contentRect = content.getBoundingClientRect(), base = getBase(view);
7166 let lineStyle = window.getComputedStyle(content.firstChild);
7167 let leftSide = contentRect.left + parseInt(lineStyle.paddingLeft) + Math.min(0, parseInt(lineStyle.textIndent));
7168 let rightSide = contentRect.right - parseInt(lineStyle.paddingRight);
7169 let startBlock = blockAt(view, from), endBlock = blockAt(view, to);
7170 let visualStart = startBlock.type == BlockType.Text ? startBlock : null;
7171 let visualEnd = endBlock.type == BlockType.Text ? endBlock : null;
7172 if (view.lineWrapping) {
7173 if (visualStart)
7174 visualStart = wrappedLine(view, from, visualStart);
7175 if (visualEnd)
7176 visualEnd = wrappedLine(view, to, visualEnd);
7177 }
7178 if (visualStart && visualEnd && visualStart.from == visualEnd.from) {
7179 return pieces(drawForLine(range.from, range.to, visualStart));
7180 }
7181 else {
7182 let top = visualStart ? drawForLine(range.from, null, visualStart) : drawForWidget(startBlock, false);
7183 let bottom = visualEnd ? drawForLine(null, range.to, visualEnd) : drawForWidget(endBlock, true);
7184 let between = [];
7185 if ((visualStart || startBlock).to < (visualEnd || endBlock).from - 1)
7186 between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
7187 else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == BlockType.Text)
7188 top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
7189 return pieces(top).concat(between).concat(pieces(bottom));
7190 }
7191 function piece(left, top, right, bottom) {
7192 return new Piece(left - base.left, top - base.top - 0.01 /* Epsilon */, right - left, bottom - top + 0.01 /* Epsilon */, "cm-selectionBackground");
7193 }
7194 function pieces({ top, bottom, horizontal }) {
7195 let pieces = [];
7196 for (let i = 0; i < horizontal.length; i += 2)
7197 pieces.push(piece(horizontal[i], top, horizontal[i + 1], bottom));
7198 return pieces;
7199 }
7200 // Gets passed from/to in line-local positions
7201 function drawForLine(from, to, line) {
7202 let top = 1e9, bottom = -1e9, horizontal = [];
7203 function addSpan(from, fromOpen, to, toOpen, dir) {
7204 // Passing 2/-2 is a kludge to force the view to return
7205 // coordinates on the proper side of block widgets, since
7206 // normalizing the side there, though appropriate for most
7207 // coordsAtPos queries, would break selection drawing.
7208 let fromCoords = view.coordsAtPos(from, (from == line.to ? -2 : 2));
7209 let toCoords = view.coordsAtPos(to, (to == line.from ? 2 : -2));
7210 top = Math.min(fromCoords.top, toCoords.top, top);
7211 bottom = Math.max(fromCoords.bottom, toCoords.bottom, bottom);
7212 if (dir == Direction.LTR)
7213 horizontal.push(ltr && fromOpen ? leftSide : fromCoords.left, ltr && toOpen ? rightSide : toCoords.right);
7214 else
7215 horizontal.push(!ltr && toOpen ? leftSide : toCoords.left, !ltr && fromOpen ? rightSide : fromCoords.right);
7216 }
7217 let start = from !== null && from !== void 0 ? from : line.from, end = to !== null && to !== void 0 ? to : line.to;
7218 // Split the range by visible range and document line
7219 for (let r of view.visibleRanges)
7220 if (r.to > start && r.from < end) {
7221 for (let pos = Math.max(r.from, start), endPos = Math.min(r.to, end);;) {
7222 let docLine = view.state.doc.lineAt(pos);
7223 for (let span of view.bidiSpans(docLine)) {
7224 let spanFrom = span.from + docLine.from, spanTo = span.to + docLine.from;
7225 if (spanFrom >= endPos)
7226 break;
7227 if (spanTo > pos)
7228 addSpan(Math.max(spanFrom, pos), from == null && spanFrom <= start, Math.min(spanTo, endPos), to == null && spanTo >= end, span.dir);
7229 }
7230 pos = docLine.to + 1;
7231 if (pos >= endPos)
7232 break;
7233 }
7234 }
7235 if (horizontal.length == 0)
7236 addSpan(start, from == null, end, to == null, view.textDirection);
7237 return { top, bottom, horizontal };
7238 }
7239 function drawForWidget(block, top) {
7240 let y = contentRect.top + (top ? block.top : block.bottom);
7241 return { top: y, bottom: y, horizontal: [] };
7242 }
7243}
7244function measureCursor(view, cursor, primary) {
7245 let pos = view.coordsAtPos(cursor.head, cursor.assoc || 1);
7246 if (!pos)
7247 return null;
7248 let base = getBase(view);
7249 return new Piece(pos.left - base.left, pos.top - base.top, -1, pos.bottom - pos.top, primary ? "cm-cursor cm-cursor-primary" : "cm-cursor cm-cursor-secondary");
7250}
7251
7252const setDropCursorPos = /*@__PURE__*/StateEffect.define({
7253 map(pos, mapping) { return pos == null ? null : mapping.mapPos(pos); }
7254});
7255const dropCursorPos = /*@__PURE__*/StateField.define({
7256 create() { return null; },
7257 update(pos, tr) {
7258 if (pos != null)
7259 pos = tr.changes.mapPos(pos);
7260 return tr.effects.reduce((pos, e) => e.is(setDropCursorPos) ? e.value : pos, pos);
7261 }
7262});
7263const drawDropCursor = /*@__PURE__*/ViewPlugin.fromClass(class {
7264 constructor(view) {
7265 this.view = view;
7266 this.cursor = null;
7267 this.measureReq = { read: this.readPos.bind(this), write: this.drawCursor.bind(this) };
7268 }
7269 update(update) {
7270 var _a;
7271 let cursorPos = update.state.field(dropCursorPos);
7272 if (cursorPos == null) {
7273 if (this.cursor != null) {
7274 (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.remove();
7275 this.cursor = null;
7276 }
7277 }
7278 else {
7279 if (!this.cursor) {
7280 this.cursor = this.view.scrollDOM.appendChild(document.createElement("div"));
7281 this.cursor.className = "cm-dropCursor";
7282 }
7283 if (update.startState.field(dropCursorPos) != cursorPos || update.docChanged || update.geometryChanged)
7284 this.view.requestMeasure(this.measureReq);
7285 }
7286 }
7287 readPos() {
7288 let pos = this.view.state.field(dropCursorPos);
7289 let rect = pos != null && this.view.coordsAtPos(pos);
7290 if (!rect)
7291 return null;
7292 let outer = this.view.scrollDOM.getBoundingClientRect();
7293 return {
7294 left: rect.left - outer.left + this.view.scrollDOM.scrollLeft,
7295 top: rect.top - outer.top + this.view.scrollDOM.scrollTop,
7296 height: rect.bottom - rect.top
7297 };
7298 }
7299 drawCursor(pos) {
7300 if (this.cursor) {
7301 if (pos) {
7302 this.cursor.style.left = pos.left + "px";
7303 this.cursor.style.top = pos.top + "px";
7304 this.cursor.style.height = pos.height + "px";
7305 }
7306 else {
7307 this.cursor.style.left = "-100000px";
7308 }
7309 }
7310 }
7311 destroy() {
7312 if (this.cursor)
7313 this.cursor.remove();
7314 }
7315 setDropPos(pos) {
7316 if (this.view.state.field(dropCursorPos) != pos)
7317 this.view.dispatch({ effects: setDropCursorPos.of(pos) });
7318 }
7319}, {
7320 eventHandlers: {
7321 dragover(event) {
7322 this.setDropPos(this.view.posAtCoords({ x: event.clientX, y: event.clientY }));
7323 },
7324 dragleave(event) {
7325 if (event.target == this.view.contentDOM || !this.view.contentDOM.contains(event.relatedTarget))
7326 this.setDropPos(null);
7327 },
7328 dragend() {
7329 this.setDropPos(null);
7330 },
7331 drop() {
7332 this.setDropPos(null);
7333 }
7334 }
7335});
7336/**
7337Draws a cursor at the current drop position when something is
7338dragged over the editor.
7339*/
7340function dropCursor() {
7341 return [dropCursorPos, drawDropCursor];
7342}
7343
7344function iterMatches(doc, re, from, to, f) {
7345 re.lastIndex = 0;
7346 for (let cursor = doc.iterRange(from, to), pos = from, m; !cursor.next().done; pos += cursor.value.length) {
7347 if (!cursor.lineBreak)
7348 while (m = re.exec(cursor.value))
7349 f(pos + m.index, pos + m.index + m[0].length, m);
7350 }
7351}
7352function matchRanges(view, maxLength) {
7353 let visible = view.visibleRanges;
7354 if (visible.length == 1 && visible[0].from == view.viewport.from &&
7355 visible[0].to == view.viewport.to)
7356 return visible;
7357 let result = [];
7358 for (let { from, to } of visible) {
7359 from = Math.max(view.state.doc.lineAt(from).from, from - maxLength);
7360 to = Math.min(view.state.doc.lineAt(to).to, to + maxLength);
7361 if (result.length && result[result.length - 1].to >= from)
7362 result[result.length - 1].to = to;
7363 else
7364 result.push({ from, to });
7365 }
7366 return result;
7367}
7368/**
7369Helper class used to make it easier to maintain decorations on
7370visible code that matches a given regular expression. To be used
7371in a [view plugin](https://codemirror.net/6/docs/ref/#view.ViewPlugin). Instances of this object
7372represent a matching configuration.
7373*/
7374class MatchDecorator {
7375 /**
7376 Create a decorator.
7377 */
7378 constructor(config) {
7379 let { regexp, decoration, boundary, maxLength = 1000 } = config;
7380 if (!regexp.global)
7381 throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
7382 this.regexp = regexp;
7383 this.getDeco = typeof decoration == "function" ? decoration : () => decoration;
7384 this.boundary = boundary;
7385 this.maxLength = maxLength;
7386 }
7387 /**
7388 Compute the full set of decorations for matches in the given
7389 view's viewport. You'll want to call this when initializing your
7390 plugin.
7391 */
7392 createDeco(view) {
7393 let build = new RangeSetBuilder();
7394 for (let { from, to } of matchRanges(view, this.maxLength))
7395 iterMatches(view.state.doc, this.regexp, from, to, (a, b, m) => build.add(a, b, this.getDeco(m, view, a)));
7396 return build.finish();
7397 }
7398 /**
7399 Update a set of decorations for a view update. `deco` _must_ be
7400 the set of decorations produced by _this_ `MatchDecorator` for
7401 the view state before the update.
7402 */
7403 updateDeco(update, deco) {
7404 let changeFrom = 1e9, changeTo = -1;
7405 if (update.docChanged)
7406 update.changes.iterChanges((_f, _t, from, to) => {
7407 if (to > update.view.viewport.from && from < update.view.viewport.to) {
7408 changeFrom = Math.min(from, changeFrom);
7409 changeTo = Math.max(to, changeTo);
7410 }
7411 });
7412 if (update.viewportChanged || changeTo - changeFrom > 1000)
7413 return this.createDeco(update.view);
7414 if (changeTo > -1)
7415 return this.updateRange(update.view, deco.map(update.changes), changeFrom, changeTo);
7416 return deco;
7417 }
7418 updateRange(view, deco, updateFrom, updateTo) {
7419 for (let r of view.visibleRanges) {
7420 let from = Math.max(r.from, updateFrom), to = Math.min(r.to, updateTo);
7421 if (to > from) {
7422 let fromLine = view.state.doc.lineAt(from), toLine = fromLine.to < to ? view.state.doc.lineAt(to) : fromLine;
7423 let start = Math.max(r.from, fromLine.from), end = Math.min(r.to, toLine.to);
7424 if (this.boundary) {
7425 for (; from > fromLine.from; from--)
7426 if (this.boundary.test(fromLine.text[from - 1 - fromLine.from])) {
7427 start = from;
7428 break;
7429 }
7430 for (; to < toLine.to; to++)
7431 if (this.boundary.test(toLine.text[to - toLine.from])) {
7432 end = to;
7433 break;
7434 }
7435 }
7436 let ranges = [], m;
7437 if (fromLine == toLine) {
7438 this.regexp.lastIndex = start - fromLine.from;
7439 while ((m = this.regexp.exec(fromLine.text)) && m.index < end - fromLine.from) {
7440 let pos = m.index + fromLine.from;
7441 ranges.push(this.getDeco(m, view, pos).range(pos, pos + m[0].length));
7442 }
7443 }
7444 else {
7445 iterMatches(view.state.doc, this.regexp, start, end, (from, to, m) => ranges.push(this.getDeco(m, view, from).range(from, to)));
7446 }
7447 deco = deco.update({ filterFrom: start, filterTo: end, filter: (from, to) => from < start || to > end, add: ranges });
7448 }
7449 }
7450 return deco;
7451 }
7452}
7453
7454const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g";
7455const Specials = /*@__PURE__*/new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc]", UnicodeRegexpSupport);
7456const Names = {
7457 0: "null",
7458 7: "bell",
7459 8: "backspace",
7460 10: "newline",
7461 11: "vertical tab",
7462 13: "carriage return",
7463 27: "escape",
7464 8203: "zero width space",
7465 8204: "zero width non-joiner",
7466 8205: "zero width joiner",
7467 8206: "left-to-right mark",
7468 8207: "right-to-left mark",
7469 8232: "line separator",
7470 8237: "left-to-right override",
7471 8238: "right-to-left override",
7472 8233: "paragraph separator",
7473 65279: "zero width no-break space",
7474 65532: "object replacement"
7475};
7476let _supportsTabSize = null;
7477function supportsTabSize() {
7478 var _a;
7479 if (_supportsTabSize == null && typeof document != "undefined" && document.body) {
7480 let styles = document.body.style;
7481 _supportsTabSize = ((_a = styles.tabSize) !== null && _a !== void 0 ? _a : styles.MozTabSize) != null;
7482 }
7483 return _supportsTabSize || false;
7484}
7485const specialCharConfig = /*@__PURE__*/Facet.define({
7486 combine(configs) {
7487 let config = combineConfig(configs, {
7488 render: null,
7489 specialChars: Specials,
7490 addSpecialChars: null
7491 });
7492 if (config.replaceTabs = !supportsTabSize())
7493 config.specialChars = new RegExp("\t|" + config.specialChars.source, UnicodeRegexpSupport);
7494 if (config.addSpecialChars)
7495 config.specialChars = new RegExp(config.specialChars.source + "|" + config.addSpecialChars.source, UnicodeRegexpSupport);
7496 return config;
7497 }
7498});
7499/**
7500Returns an extension that installs highlighting of special
7501characters.
7502*/
7503function highlightSpecialChars(
7504/**
7505Configuration options.
7506*/
7507config = {}) {
7508 return [specialCharConfig.of(config), specialCharPlugin()];
7509}
7510let _plugin = null;
7511function specialCharPlugin() {
7512 return _plugin || (_plugin = ViewPlugin.fromClass(class {
7513 constructor(view) {
7514 this.view = view;
7515 this.decorations = Decoration.none;
7516 this.decorationCache = Object.create(null);
7517 this.decorator = this.makeDecorator(view.state.facet(specialCharConfig));
7518 this.decorations = this.decorator.createDeco(view);
7519 }
7520 makeDecorator(conf) {
7521 return new MatchDecorator({
7522 regexp: conf.specialChars,
7523 decoration: (m, view, pos) => {
7524 let { doc } = view.state;
7525 let code = codePointAt(m[0], 0);
7526 if (code == 9) {
7527 let line = doc.lineAt(pos);
7528 let size = view.state.tabSize, col = countColumn(line.text, size, pos - line.from);
7529 return Decoration.replace({ widget: new TabWidget((size - (col % size)) * this.view.defaultCharacterWidth) });
7530 }
7531 return this.decorationCache[code] ||
7532 (this.decorationCache[code] = Decoration.replace({ widget: new SpecialCharWidget(conf, code) }));
7533 },
7534 boundary: conf.replaceTabs ? undefined : /[^]/
7535 });
7536 }
7537 update(update) {
7538 let conf = update.state.facet(specialCharConfig);
7539 if (update.startState.facet(specialCharConfig) != conf) {
7540 this.decorator = this.makeDecorator(conf);
7541 this.decorations = this.decorator.createDeco(update.view);
7542 }
7543 else {
7544 this.decorations = this.decorator.updateDeco(update, this.decorations);
7545 }
7546 }
7547 }, {
7548 decorations: v => v.decorations
7549 }));
7550}
7551const DefaultPlaceholder = "\u2022";
7552// Assigns placeholder characters from the Control Pictures block to
7553// ASCII control characters
7554function placeholder$1(code) {
7555 if (code >= 32)
7556 return DefaultPlaceholder;
7557 if (code == 10)
7558 return "\u2424";
7559 return String.fromCharCode(9216 + code);
7560}
7561class SpecialCharWidget extends WidgetType {
7562 constructor(options, code) {
7563 super();
7564 this.options = options;
7565 this.code = code;
7566 }
7567 eq(other) { return other.code == this.code; }
7568 toDOM(view) {
7569 let ph = placeholder$1(this.code);
7570 let desc = view.state.phrase("Control character") + " " + (Names[this.code] || "0x" + this.code.toString(16));
7571 let custom = this.options.render && this.options.render(this.code, desc, ph);
7572 if (custom)
7573 return custom;
7574 let span = document.createElement("span");
7575 span.textContent = ph;
7576 span.title = desc;
7577 span.setAttribute("aria-label", desc);
7578 span.className = "cm-specialChar";
7579 return span;
7580 }
7581 ignoreEvent() { return false; }
7582}
7583class TabWidget extends WidgetType {
7584 constructor(width) {
7585 super();
7586 this.width = width;
7587 }
7588 eq(other) { return other.width == this.width; }
7589 toDOM() {
7590 let span = document.createElement("span");
7591 span.textContent = "\t";
7592 span.className = "cm-tab";
7593 span.style.width = this.width + "px";
7594 return span;
7595 }
7596 ignoreEvent() { return false; }
7597}
7598
7599const plugin = /*@__PURE__*/ViewPlugin.fromClass(class {
7600 constructor() {
7601 this.height = 1000;
7602 this.attrs = { style: "padding-bottom: 1000px" };
7603 }
7604 update(update) {
7605 let height = update.view.viewState.editorHeight - update.view.defaultLineHeight;
7606 if (height != this.height) {
7607 this.height = height;
7608 this.attrs = { style: `padding-bottom: ${height}px` };
7609 }
7610 }
7611});
7612/**
7613Returns an extension that makes sure the content has a bottom
7614margin equivalent to the height of the editor, minus one line
7615height, so that every line in the document can be scrolled to the
7616top of the editor.
7617
7618This is only meaningful when the editor is scrollable, and should
7619not be enabled in editors that take the size of their content.
7620*/
7621function scrollPastEnd() {
7622 return [plugin, contentAttributes.of(view => { var _a; return ((_a = view.plugin(plugin)) === null || _a === void 0 ? void 0 : _a.attrs) || null; })];
7623}
7624
7625/**
7626Mark lines that have a cursor on them with the `"cm-activeLine"`
7627DOM class.
7628*/
7629function highlightActiveLine() {
7630 return activeLineHighlighter;
7631}
7632const lineDeco = /*@__PURE__*/Decoration.line({ class: "cm-activeLine" });
7633const activeLineHighlighter = /*@__PURE__*/ViewPlugin.fromClass(class {
7634 constructor(view) {
7635 this.decorations = this.getDeco(view);
7636 }
7637 update(update) {
7638 if (update.docChanged || update.selectionSet)
7639 this.decorations = this.getDeco(update.view);
7640 }
7641 getDeco(view) {
7642 let lastLineStart = -1, deco = [];
7643 for (let r of view.state.selection.ranges) {
7644 if (!r.empty)
7645 return Decoration.none;
7646 let line = view.lineBlockAt(r.head);
7647 if (line.from > lastLineStart) {
7648 deco.push(lineDeco.range(line.from));
7649 lastLineStart = line.from;
7650 }
7651 }
7652 return Decoration.set(deco);
7653 }
7654}, {
7655 decorations: v => v.decorations
7656});
7657
7658class Placeholder extends WidgetType {
7659 constructor(content) {
7660 super();
7661 this.content = content;
7662 }
7663 toDOM() {
7664 let wrap = document.createElement("span");
7665 wrap.className = "cm-placeholder";
7666 wrap.style.pointerEvents = "none";
7667 wrap.appendChild(typeof this.content == "string" ? document.createTextNode(this.content) : this.content);
7668 if (typeof this.content == "string")
7669 wrap.setAttribute("aria-label", "placeholder " + this.content);
7670 else
7671 wrap.setAttribute("aria-hidden", "true");
7672 return wrap;
7673 }
7674 ignoreEvent() { return false; }
7675}
7676/**
7677Extension that enables a placeholder—a piece of example content
7678to show when the editor is empty.
7679*/
7680function placeholder(content) {
7681 return ViewPlugin.fromClass(class {
7682 constructor(view) {
7683 this.view = view;
7684 this.placeholder = Decoration.set([Decoration.widget({ widget: new Placeholder(content), side: 1 }).range(0)]);
7685 }
7686 get decorations() { return this.view.state.doc.length ? Decoration.none : this.placeholder; }
7687 }, { decorations: v => v.decorations });
7688}
7689
7690// Don't compute precise column positions for line offsets above this
7691// (since it could get expensive). Assume offset==column for them.
7692const MaxOff = 2000;
7693function rectangleFor(state, a, b) {
7694 let startLine = Math.min(a.line, b.line), endLine = Math.max(a.line, b.line);
7695 let ranges = [];
7696 if (a.off > MaxOff || b.off > MaxOff || a.col < 0 || b.col < 0) {
7697 let startOff = Math.min(a.off, b.off), endOff = Math.max(a.off, b.off);
7698 for (let i = startLine; i <= endLine; i++) {
7699 let line = state.doc.line(i);
7700 if (line.length <= endOff)
7701 ranges.push(EditorSelection.range(line.from + startOff, line.to + endOff));
7702 }
7703 }
7704 else {
7705 let startCol = Math.min(a.col, b.col), endCol = Math.max(a.col, b.col);
7706 for (let i = startLine; i <= endLine; i++) {
7707 let line = state.doc.line(i);
7708 let start = findColumn(line.text, startCol, state.tabSize, true);
7709 if (start > -1) {
7710 let end = findColumn(line.text, endCol, state.tabSize);
7711 ranges.push(EditorSelection.range(line.from + start, line.from + end));
7712 }
7713 }
7714 }
7715 return ranges;
7716}
7717function absoluteColumn(view, x) {
7718 let ref = view.coordsAtPos(view.viewport.from);
7719 return ref ? Math.round(Math.abs((ref.left - x) / view.defaultCharacterWidth)) : -1;
7720}
7721function getPos(view, event) {
7722 let offset = view.posAtCoords({ x: event.clientX, y: event.clientY }, false);
7723 let line = view.state.doc.lineAt(offset), off = offset - line.from;
7724 let col = off > MaxOff ? -1
7725 : off == line.length ? absoluteColumn(view, event.clientX)
7726 : countColumn(line.text, view.state.tabSize, offset - line.from);
7727 return { line: line.number, col, off };
7728}
7729function rectangleSelectionStyle(view, event) {
7730 let start = getPos(view, event), startSel = view.state.selection;
7731 if (!start)
7732 return null;
7733 return {
7734 update(update) {
7735 if (update.docChanged) {
7736 let newStart = update.changes.mapPos(update.startState.doc.line(start.line).from);
7737 let newLine = update.state.doc.lineAt(newStart);
7738 start = { line: newLine.number, col: start.col, off: Math.min(start.off, newLine.length) };
7739 startSel = startSel.map(update.changes);
7740 }
7741 },
7742 get(event, _extend, multiple) {
7743 let cur = getPos(view, event);
7744 if (!cur)
7745 return startSel;
7746 let ranges = rectangleFor(view.state, start, cur);
7747 if (!ranges.length)
7748 return startSel;
7749 if (multiple)
7750 return EditorSelection.create(ranges.concat(startSel.ranges));
7751 else
7752 return EditorSelection.create(ranges);
7753 }
7754 };
7755}
7756/**
7757Create an extension that enables rectangular selections. By
7758default, it will react to left mouse drag with the Alt key held
7759down. When such a selection occurs, the text within the rectangle
7760that was dragged over will be selected, as one selection
7761[range](https://codemirror.net/6/docs/ref/#state.SelectionRange) per line.
7762*/
7763function rectangularSelection(options) {
7764 let filter = (options === null || options === void 0 ? void 0 : options.eventFilter) || (e => e.altKey && e.button == 0);
7765 return EditorView.mouseSelectionStyle.of((view, event) => filter(event) ? rectangleSelectionStyle(view, event) : null);
7766}
7767const keys = {
7768 Alt: [18, e => e.altKey],
7769 Control: [17, e => e.ctrlKey],
7770 Shift: [16, e => e.shiftKey],
7771 Meta: [91, e => e.metaKey]
7772};
7773const showCrosshair = { style: "cursor: crosshair" };
7774/**
7775Returns an extension that turns the pointer cursor into a
7776crosshair when a given modifier key, defaulting to Alt, is held
7777down. Can serve as a visual hint that rectangular selection is
7778going to happen when paired with
7779[`rectangularSelection`](https://codemirror.net/6/docs/ref/#view.rectangularSelection).
7780*/
7781function crosshairCursor(options = {}) {
7782 let [code, getter] = keys[options.key || "Alt"];
7783 let plugin = ViewPlugin.fromClass(class {
7784 constructor(view) {
7785 this.view = view;
7786 this.isDown = false;
7787 }
7788 set(isDown) {
7789 if (this.isDown != isDown) {
7790 this.isDown = isDown;
7791 this.view.update([]);
7792 }
7793 }
7794 }, {
7795 eventHandlers: {
7796 keydown(e) {
7797 this.set(e.keyCode == code || getter(e));
7798 },
7799 keyup(e) {
7800 if (e.keyCode == code || !getter(e))
7801 this.set(false);
7802 }
7803 }
7804 });
7805 return [
7806 plugin,
7807 EditorView.contentAttributes.of(view => { var _a; return ((_a = view.plugin(plugin)) === null || _a === void 0 ? void 0 : _a.isDown) ? showCrosshair : null; })
7808 ];
7809}
7810
7811const Outside = "-10000px";
7812class TooltipViewManager {
7813 constructor(view, facet, createTooltipView) {
7814 this.facet = facet;
7815 this.createTooltipView = createTooltipView;
7816 this.input = view.state.facet(facet);
7817 this.tooltips = this.input.filter(t => t);
7818 this.tooltipViews = this.tooltips.map(createTooltipView);
7819 }
7820 update(update) {
7821 let input = update.state.facet(this.facet);
7822 let tooltips = input.filter(x => x);
7823 if (input === this.input) {
7824 for (let t of this.tooltipViews)
7825 if (t.update)
7826 t.update(update);
7827 return false;
7828 }
7829 let tooltipViews = [];
7830 for (let i = 0; i < tooltips.length; i++) {
7831 let tip = tooltips[i], known = -1;
7832 if (!tip)
7833 continue;
7834 for (let i = 0; i < this.tooltips.length; i++) {
7835 let other = this.tooltips[i];
7836 if (other && other.create == tip.create)
7837 known = i;
7838 }
7839 if (known < 0) {
7840 tooltipViews[i] = this.createTooltipView(tip);
7841 }
7842 else {
7843 let tooltipView = tooltipViews[i] = this.tooltipViews[known];
7844 if (tooltipView.update)
7845 tooltipView.update(update);
7846 }
7847 }
7848 for (let t of this.tooltipViews)
7849 if (tooltipViews.indexOf(t) < 0)
7850 t.dom.remove();
7851 this.input = input;
7852 this.tooltips = tooltips;
7853 this.tooltipViews = tooltipViews;
7854 return true;
7855 }
7856}
7857/**
7858Creates an extension that configures tooltip behavior.
7859*/
7860function tooltips(config = {}) {
7861 return tooltipConfig.of(config);
7862}
7863function windowSpace() {
7864 return { top: 0, left: 0, bottom: innerHeight, right: innerWidth };
7865}
7866const tooltipConfig = /*@__PURE__*/Facet.define({
7867 combine: values => {
7868 var _a, _b, _c;
7869 return ({
7870 position: browser.ios ? "absolute" : ((_a = values.find(conf => conf.position)) === null || _a === void 0 ? void 0 : _a.position) || "fixed",
7871 parent: ((_b = values.find(conf => conf.parent)) === null || _b === void 0 ? void 0 : _b.parent) || null,
7872 tooltipSpace: ((_c = values.find(conf => conf.tooltipSpace)) === null || _c === void 0 ? void 0 : _c.tooltipSpace) || windowSpace,
7873 });
7874 }
7875});
7876const tooltipPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
7877 constructor(view) {
7878 var _a;
7879 this.view = view;
7880 this.inView = true;
7881 this.lastTransaction = 0;
7882 this.measureTimeout = -1;
7883 let config = view.state.facet(tooltipConfig);
7884 this.position = config.position;
7885 this.parent = config.parent;
7886 this.classes = view.themeClasses;
7887 this.createContainer();
7888 this.measureReq = { read: this.readMeasure.bind(this), write: this.writeMeasure.bind(this), key: this };
7889 this.manager = new TooltipViewManager(view, showTooltip, t => this.createTooltip(t));
7890 this.intersectionObserver = typeof IntersectionObserver == "function" ? new IntersectionObserver(entries => {
7891 if (Date.now() > this.lastTransaction - 50 &&
7892 entries.length > 0 && entries[entries.length - 1].intersectionRatio < 1)
7893 this.measureSoon();
7894 }, { threshold: [1] }) : null;
7895 this.observeIntersection();
7896 (_a = view.dom.ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.addEventListener("resize", this.measureSoon = this.measureSoon.bind(this));
7897 this.maybeMeasure();
7898 }
7899 createContainer() {
7900 if (this.parent) {
7901 this.container = document.createElement("div");
7902 this.container.style.position = "relative";
7903 this.container.className = this.view.themeClasses;
7904 this.parent.appendChild(this.container);
7905 }
7906 else {
7907 this.container = this.view.dom;
7908 }
7909 }
7910 observeIntersection() {
7911 if (this.intersectionObserver) {
7912 this.intersectionObserver.disconnect();
7913 for (let tooltip of this.manager.tooltipViews)
7914 this.intersectionObserver.observe(tooltip.dom);
7915 }
7916 }
7917 measureSoon() {
7918 if (this.measureTimeout < 0)
7919 this.measureTimeout = setTimeout(() => {
7920 this.measureTimeout = -1;
7921 this.maybeMeasure();
7922 }, 50);
7923 }
7924 update(update) {
7925 if (update.transactions.length)
7926 this.lastTransaction = Date.now();
7927 let updated = this.manager.update(update);
7928 if (updated)
7929 this.observeIntersection();
7930 let shouldMeasure = updated || update.geometryChanged;
7931 let newConfig = update.state.facet(tooltipConfig);
7932 if (newConfig.position != this.position) {
7933 this.position = newConfig.position;
7934 for (let t of this.manager.tooltipViews)
7935 t.dom.style.position = this.position;
7936 shouldMeasure = true;
7937 }
7938 if (newConfig.parent != this.parent) {
7939 if (this.parent)
7940 this.container.remove();
7941 this.parent = newConfig.parent;
7942 this.createContainer();
7943 for (let t of this.manager.tooltipViews)
7944 this.container.appendChild(t.dom);
7945 shouldMeasure = true;
7946 }
7947 else if (this.parent && this.view.themeClasses != this.classes) {
7948 this.classes = this.container.className = this.view.themeClasses;
7949 }
7950 if (shouldMeasure)
7951 this.maybeMeasure();
7952 }
7953 createTooltip(tooltip) {
7954 let tooltipView = tooltip.create(this.view);
7955 tooltipView.dom.classList.add("cm-tooltip");
7956 if (tooltip.arrow && !tooltipView.dom.querySelector(".cm-tooltip > .cm-tooltip-arrow")) {
7957 let arrow = document.createElement("div");
7958 arrow.className = "cm-tooltip-arrow";
7959 tooltipView.dom.appendChild(arrow);
7960 }
7961 tooltipView.dom.style.position = this.position;
7962 tooltipView.dom.style.top = Outside;
7963 this.container.appendChild(tooltipView.dom);
7964 if (tooltipView.mount)
7965 tooltipView.mount(this.view);
7966 return tooltipView;
7967 }
7968 destroy() {
7969 var _a, _b;
7970 (_a = this.view.dom.ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.removeEventListener("resize", this.measureSoon);
7971 for (let { dom } of this.manager.tooltipViews)
7972 dom.remove();
7973 (_b = this.intersectionObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
7974 clearTimeout(this.measureTimeout);
7975 }
7976 readMeasure() {
7977 let editor = this.view.dom.getBoundingClientRect();
7978 return {
7979 editor,
7980 parent: this.parent ? this.container.getBoundingClientRect() : editor,
7981 pos: this.manager.tooltips.map((t, i) => {
7982 let tv = this.manager.tooltipViews[i];
7983 return tv.getCoords ? tv.getCoords(t.pos) : this.view.coordsAtPos(t.pos);
7984 }),
7985 size: this.manager.tooltipViews.map(({ dom }) => dom.getBoundingClientRect()),
7986 space: this.view.state.facet(tooltipConfig).tooltipSpace(this.view),
7987 };
7988 }
7989 writeMeasure(measured) {
7990 let { editor, space } = measured;
7991 let others = [];
7992 for (let i = 0; i < this.manager.tooltips.length; i++) {
7993 let tooltip = this.manager.tooltips[i], tView = this.manager.tooltipViews[i], { dom } = tView;
7994 let pos = measured.pos[i], size = measured.size[i];
7995 // Hide tooltips that are outside of the editor.
7996 if (!pos || pos.bottom <= Math.max(editor.top, space.top) ||
7997 pos.top >= Math.min(editor.bottom, space.bottom) ||
7998 pos.right < Math.max(editor.left, space.left) - .1 ||
7999 pos.left > Math.min(editor.right, space.right) + .1) {
8000 dom.style.top = Outside;
8001 continue;
8002 }
8003 let arrow = tooltip.arrow ? tView.dom.querySelector(".cm-tooltip-arrow") : null;
8004 let arrowHeight = arrow ? 7 /* Size */ : 0;
8005 let width = size.right - size.left, height = size.bottom - size.top;
8006 let offset = tView.offset || noOffset, ltr = this.view.textDirection == Direction.LTR;
8007 let left = size.width > space.right - space.left ? (ltr ? space.left : space.right - size.width)
8008 : ltr ? Math.min(pos.left - (arrow ? 14 /* Offset */ : 0) + offset.x, space.right - width)
8009 : Math.max(space.left, pos.left - width + (arrow ? 14 /* Offset */ : 0) - offset.x);
8010 let above = !!tooltip.above;
8011 if (!tooltip.strictSide && (above
8012 ? pos.top - (size.bottom - size.top) - offset.y < space.top
8013 : pos.bottom + (size.bottom - size.top) + offset.y > space.bottom) &&
8014 above == (space.bottom - pos.bottom > pos.top - space.top))
8015 above = !above;
8016 let top = above ? pos.top - height - arrowHeight - offset.y : pos.bottom + arrowHeight + offset.y;
8017 let right = left + width;
8018 if (tView.overlap !== true)
8019 for (let r of others)
8020 if (r.left < right && r.right > left && r.top < top + height && r.bottom > top)
8021 top = above ? r.top - height - 2 - arrowHeight : r.bottom + arrowHeight + 2;
8022 if (this.position == "absolute") {
8023 dom.style.top = (top - measured.parent.top) + "px";
8024 dom.style.left = (left - measured.parent.left) + "px";
8025 }
8026 else {
8027 dom.style.top = top + "px";
8028 dom.style.left = left + "px";
8029 }
8030 if (arrow)
8031 arrow.style.left = `${pos.left + (ltr ? offset.x : -offset.x) - (left + 14 /* Offset */ - 7 /* Size */)}px`;
8032 if (tView.overlap !== true)
8033 others.push({ left, top, right, bottom: top + height });
8034 dom.classList.toggle("cm-tooltip-above", above);
8035 dom.classList.toggle("cm-tooltip-below", !above);
8036 if (tView.positioned)
8037 tView.positioned();
8038 }
8039 }
8040 maybeMeasure() {
8041 if (this.manager.tooltips.length) {
8042 if (this.view.inView)
8043 this.view.requestMeasure(this.measureReq);
8044 if (this.inView != this.view.inView) {
8045 this.inView = this.view.inView;
8046 if (!this.inView)
8047 for (let tv of this.manager.tooltipViews)
8048 tv.dom.style.top = Outside;
8049 }
8050 }
8051 }
8052}, {
8053 eventHandlers: {
8054 scroll() { this.maybeMeasure(); }
8055 }
8056});
8057const baseTheme = /*@__PURE__*/EditorView.baseTheme({
8058 ".cm-tooltip": {
8059 zIndex: 100
8060 },
8061 "&light .cm-tooltip": {
8062 border: "1px solid #bbb",
8063 backgroundColor: "#f5f5f5"
8064 },
8065 "&light .cm-tooltip-section:not(:first-child)": {
8066 borderTop: "1px solid #bbb",
8067 },
8068 "&dark .cm-tooltip": {
8069 backgroundColor: "#333338",
8070 color: "white"
8071 },
8072 ".cm-tooltip-arrow": {
8073 height: `${7 /* Size */}px`,
8074 width: `${7 /* Size */ * 2}px`,
8075 position: "absolute",
8076 zIndex: -1,
8077 overflow: "hidden",
8078 "&:before, &:after": {
8079 content: "''",
8080 position: "absolute",
8081 width: 0,
8082 height: 0,
8083 borderLeft: `${7 /* Size */}px solid transparent`,
8084 borderRight: `${7 /* Size */}px solid transparent`,
8085 },
8086 ".cm-tooltip-above &": {
8087 bottom: `-${7 /* Size */}px`,
8088 "&:before": {
8089 borderTop: `${7 /* Size */}px solid #bbb`,
8090 },
8091 "&:after": {
8092 borderTop: `${7 /* Size */}px solid #f5f5f5`,
8093 bottom: "1px"
8094 }
8095 },
8096 ".cm-tooltip-below &": {
8097 top: `-${7 /* Size */}px`,
8098 "&:before": {
8099 borderBottom: `${7 /* Size */}px solid #bbb`,
8100 },
8101 "&:after": {
8102 borderBottom: `${7 /* Size */}px solid #f5f5f5`,
8103 top: "1px"
8104 }
8105 },
8106 },
8107 "&dark .cm-tooltip .cm-tooltip-arrow": {
8108 "&:before": {
8109 borderTopColor: "#333338",
8110 borderBottomColor: "#333338"
8111 },
8112 "&:after": {
8113 borderTopColor: "transparent",
8114 borderBottomColor: "transparent"
8115 }
8116 }
8117});
8118const noOffset = { x: 0, y: 0 };
8119/**
8120Facet to which an extension can add a value to show a tooltip.
8121*/
8122const showTooltip = /*@__PURE__*/Facet.define({
8123 enables: [tooltipPlugin, baseTheme]
8124});
8125const showHoverTooltip = /*@__PURE__*/Facet.define();
8126class HoverTooltipHost {
8127 constructor(view) {
8128 this.view = view;
8129 this.mounted = false;
8130 this.dom = document.createElement("div");
8131 this.dom.classList.add("cm-tooltip-hover");
8132 this.manager = new TooltipViewManager(view, showHoverTooltip, t => this.createHostedView(t));
8133 }
8134 // Needs to be static so that host tooltip instances always match
8135 static create(view) {
8136 return new HoverTooltipHost(view);
8137 }
8138 createHostedView(tooltip) {
8139 let hostedView = tooltip.create(this.view);
8140 hostedView.dom.classList.add("cm-tooltip-section");
8141 this.dom.appendChild(hostedView.dom);
8142 if (this.mounted && hostedView.mount)
8143 hostedView.mount(this.view);
8144 return hostedView;
8145 }
8146 mount(view) {
8147 for (let hostedView of this.manager.tooltipViews) {
8148 if (hostedView.mount)
8149 hostedView.mount(view);
8150 }
8151 this.mounted = true;
8152 }
8153 positioned() {
8154 for (let hostedView of this.manager.tooltipViews) {
8155 if (hostedView.positioned)
8156 hostedView.positioned();
8157 }
8158 }
8159 update(update) {
8160 this.manager.update(update);
8161 }
8162}
8163const showHoverTooltipHost = /*@__PURE__*/showTooltip.compute([showHoverTooltip], state => {
8164 let tooltips = state.facet(showHoverTooltip).filter(t => t);
8165 if (tooltips.length === 0)
8166 return null;
8167 return {
8168 pos: Math.min(...tooltips.map(t => t.pos)),
8169 end: Math.max(...tooltips.filter(t => t.end != null).map(t => t.end)),
8170 create: HoverTooltipHost.create,
8171 above: tooltips[0].above,
8172 arrow: tooltips.some(t => t.arrow),
8173 };
8174});
8175class HoverPlugin {
8176 constructor(view, source, field, setHover, hoverTime) {
8177 this.view = view;
8178 this.source = source;
8179 this.field = field;
8180 this.setHover = setHover;
8181 this.hoverTime = hoverTime;
8182 this.hoverTimeout = -1;
8183 this.restartTimeout = -1;
8184 this.pending = null;
8185 this.lastMove = { x: 0, y: 0, target: view.dom, time: 0 };
8186 this.checkHover = this.checkHover.bind(this);
8187 view.dom.addEventListener("mouseleave", this.mouseleave = this.mouseleave.bind(this));
8188 view.dom.addEventListener("mousemove", this.mousemove = this.mousemove.bind(this));
8189 }
8190 update() {
8191 if (this.pending) {
8192 this.pending = null;
8193 clearTimeout(this.restartTimeout);
8194 this.restartTimeout = setTimeout(() => this.startHover(), 20);
8195 }
8196 }
8197 get active() {
8198 return this.view.state.field(this.field);
8199 }
8200 checkHover() {
8201 this.hoverTimeout = -1;
8202 if (this.active)
8203 return;
8204 let hovered = Date.now() - this.lastMove.time;
8205 if (hovered < this.hoverTime)
8206 this.hoverTimeout = setTimeout(this.checkHover, this.hoverTime - hovered);
8207 else
8208 this.startHover();
8209 }
8210 startHover() {
8211 clearTimeout(this.restartTimeout);
8212 let { lastMove } = this;
8213 let pos = this.view.contentDOM.contains(lastMove.target) ? this.view.posAtCoords(lastMove) : null;
8214 if (pos == null)
8215 return;
8216 let posCoords = this.view.coordsAtPos(pos);
8217 if (posCoords == null || lastMove.y < posCoords.top || lastMove.y > posCoords.bottom ||
8218 lastMove.x < posCoords.left - this.view.defaultCharacterWidth ||
8219 lastMove.x > posCoords.right + this.view.defaultCharacterWidth)
8220 return;
8221 let bidi = this.view.bidiSpans(this.view.state.doc.lineAt(pos)).find(s => s.from <= pos && s.to >= pos);
8222 let rtl = bidi && bidi.dir == Direction.RTL ? -1 : 1;
8223 let open = this.source(this.view, pos, (lastMove.x < posCoords.left ? -rtl : rtl));
8224 if (open === null || open === void 0 ? void 0 : open.then) {
8225 let pending = this.pending = { pos };
8226 open.then(result => {
8227 if (this.pending == pending) {
8228 this.pending = null;
8229 if (result)
8230 this.view.dispatch({ effects: this.setHover.of(result) });
8231 }
8232 }, e => logException(this.view.state, e, "hover tooltip"));
8233 }
8234 else if (open) {
8235 this.view.dispatch({ effects: this.setHover.of(open) });
8236 }
8237 }
8238 mousemove(event) {
8239 var _a;
8240 this.lastMove = { x: event.clientX, y: event.clientY, target: event.target, time: Date.now() };
8241 if (this.hoverTimeout < 0)
8242 this.hoverTimeout = setTimeout(this.checkHover, this.hoverTime);
8243 let tooltip = this.active;
8244 if (tooltip && !isInTooltip(this.lastMove.target) || this.pending) {
8245 let { pos } = tooltip || this.pending, end = (_a = tooltip === null || tooltip === void 0 ? void 0 : tooltip.end) !== null && _a !== void 0 ? _a : pos;
8246 if ((pos == end ? this.view.posAtCoords(this.lastMove) != pos
8247 : !isOverRange(this.view, pos, end, event.clientX, event.clientY, 6 /* MaxDist */))) {
8248 this.view.dispatch({ effects: this.setHover.of(null) });
8249 this.pending = null;
8250 }
8251 }
8252 }
8253 mouseleave() {
8254 clearTimeout(this.hoverTimeout);
8255 this.hoverTimeout = -1;
8256 if (this.active)
8257 this.view.dispatch({ effects: this.setHover.of(null) });
8258 }
8259 destroy() {
8260 clearTimeout(this.hoverTimeout);
8261 this.view.dom.removeEventListener("mouseleave", this.mouseleave);
8262 this.view.dom.removeEventListener("mousemove", this.mousemove);
8263 }
8264}
8265function isInTooltip(elt) {
8266 for (let cur = elt; cur; cur = cur.parentNode)
8267 if (cur.nodeType == 1 && cur.classList.contains("cm-tooltip"))
8268 return true;
8269 return false;
8270}
8271function isOverRange(view, from, to, x, y, margin) {
8272 let range = document.createRange();
8273 let fromDOM = view.domAtPos(from), toDOM = view.domAtPos(to);
8274 range.setEnd(toDOM.node, toDOM.offset);
8275 range.setStart(fromDOM.node, fromDOM.offset);
8276 let rects = range.getClientRects();
8277 range.detach();
8278 for (let i = 0; i < rects.length; i++) {
8279 let rect = rects[i];
8280 let dist = Math.max(rect.top - y, y - rect.bottom, rect.left - x, x - rect.right);
8281 if (dist <= margin)
8282 return true;
8283 }
8284 return false;
8285}
8286/**
8287Set up a hover tooltip, which shows up when the pointer hovers
8288over ranges of text. The callback is called when the mouse hovers
8289over the document text. It should, if there is a tooltip
8290associated with position `pos`, return the tooltip description
8291(either directly or in a promise). The `side` argument indicates
8292on which side of the position the pointer is—it will be -1 if the
8293pointer is before the position, 1 if after the position.
8294
8295Note that all hover tooltips are hosted within a single tooltip
8296container element. This allows multiple tooltips over the same
8297range to be "merged" together without overlapping.
8298*/
8299function hoverTooltip(source, options = {}) {
8300 let setHover = StateEffect.define();
8301 let hoverState = StateField.define({
8302 create() { return null; },
8303 update(value, tr) {
8304 if (value && (options.hideOnChange && (tr.docChanged || tr.selection) ||
8305 options.hideOn && options.hideOn(tr, value)))
8306 return null;
8307 if (value && tr.docChanged) {
8308 let newPos = tr.changes.mapPos(value.pos, -1, MapMode.TrackDel);
8309 if (newPos == null)
8310 return null;
8311 let copy = Object.assign(Object.create(null), value);
8312 copy.pos = newPos;
8313 if (value.end != null)
8314 copy.end = tr.changes.mapPos(value.end);
8315 value = copy;
8316 }
8317 for (let effect of tr.effects) {
8318 if (effect.is(setHover))
8319 value = effect.value;
8320 if (effect.is(closeHoverTooltipEffect))
8321 value = null;
8322 }
8323 return value;
8324 },
8325 provide: f => showHoverTooltip.from(f)
8326 });
8327 return [
8328 hoverState,
8329 ViewPlugin.define(view => new HoverPlugin(view, source, hoverState, setHover, options.hoverTime || 300 /* Time */)),
8330 showHoverTooltipHost
8331 ];
8332}
8333/**
8334Get the active tooltip view for a given tooltip, if available.
8335*/
8336function getTooltip(view, tooltip) {
8337 let plugin = view.plugin(tooltipPlugin);
8338 if (!plugin)
8339 return null;
8340 let found = plugin.manager.tooltips.indexOf(tooltip);
8341 return found < 0 ? null : plugin.manager.tooltipViews[found];
8342}
8343/**
8344Returns true if any hover tooltips are currently active.
8345*/
8346function hasHoverTooltips(state) {
8347 return state.facet(showHoverTooltip).some(x => x);
8348}
8349const closeHoverTooltipEffect = /*@__PURE__*/StateEffect.define();
8350/**
8351Transaction effect that closes all hover tooltips.
8352*/
8353const closeHoverTooltips = /*@__PURE__*/closeHoverTooltipEffect.of(null);
8354/**
8355Tell the tooltip extension to recompute the position of the active
8356tooltips. This can be useful when something happens (such as a
8357re-positioning or CSS change affecting the editor) that could
8358invalidate the existing tooltip positions.
8359*/
8360function repositionTooltips(view) {
8361 var _a;
8362 (_a = view.plugin(tooltipPlugin)) === null || _a === void 0 ? void 0 : _a.maybeMeasure();
8363}
8364
8365const panelConfig = /*@__PURE__*/Facet.define({
8366 combine(configs) {
8367 let topContainer, bottomContainer;
8368 for (let c of configs) {
8369 topContainer = topContainer || c.topContainer;
8370 bottomContainer = bottomContainer || c.bottomContainer;
8371 }
8372 return { topContainer, bottomContainer };
8373 }
8374});
8375/**
8376Configures the panel-managing extension.
8377*/
8378function panels(config) {
8379 return config ? [panelConfig.of(config)] : [];
8380}
8381/**
8382Get the active panel created by the given constructor, if any.
8383This can be useful when you need access to your panels' DOM
8384structure.
8385*/
8386function getPanel(view, panel) {
8387 let plugin = view.plugin(panelPlugin);
8388 let index = plugin ? plugin.specs.indexOf(panel) : -1;
8389 return index > -1 ? plugin.panels[index] : null;
8390}
8391const panelPlugin = /*@__PURE__*/ViewPlugin.fromClass(class {
8392 constructor(view) {
8393 this.input = view.state.facet(showPanel);
8394 this.specs = this.input.filter(s => s);
8395 this.panels = this.specs.map(spec => spec(view));
8396 let conf = view.state.facet(panelConfig);
8397 this.top = new PanelGroup(view, true, conf.topContainer);
8398 this.bottom = new PanelGroup(view, false, conf.bottomContainer);
8399 this.top.sync(this.panels.filter(p => p.top));
8400 this.bottom.sync(this.panels.filter(p => !p.top));
8401 for (let p of this.panels) {
8402 p.dom.classList.add("cm-panel");
8403 if (p.mount)
8404 p.mount();
8405 }
8406 }
8407 update(update) {
8408 let conf = update.state.facet(panelConfig);
8409 if (this.top.container != conf.topContainer) {
8410 this.top.sync([]);
8411 this.top = new PanelGroup(update.view, true, conf.topContainer);
8412 }
8413 if (this.bottom.container != conf.bottomContainer) {
8414 this.bottom.sync([]);
8415 this.bottom = new PanelGroup(update.view, false, conf.bottomContainer);
8416 }
8417 this.top.syncClasses();
8418 this.bottom.syncClasses();
8419 let input = update.state.facet(showPanel);
8420 if (input != this.input) {
8421 let specs = input.filter(x => x);
8422 let panels = [], top = [], bottom = [], mount = [];
8423 for (let spec of specs) {
8424 let known = this.specs.indexOf(spec), panel;
8425 if (known < 0) {
8426 panel = spec(update.view);
8427 mount.push(panel);
8428 }
8429 else {
8430 panel = this.panels[known];
8431 if (panel.update)
8432 panel.update(update);
8433 }
8434 panels.push(panel);
8435 (panel.top ? top : bottom).push(panel);
8436 }
8437 this.specs = specs;
8438 this.panels = panels;
8439 this.top.sync(top);
8440 this.bottom.sync(bottom);
8441 for (let p of mount) {
8442 p.dom.classList.add("cm-panel");
8443 if (p.mount)
8444 p.mount();
8445 }
8446 }
8447 else {
8448 for (let p of this.panels)
8449 if (p.update)
8450 p.update(update);
8451 }
8452 }
8453 destroy() {
8454 this.top.sync([]);
8455 this.bottom.sync([]);
8456 }
8457}, {
8458 provide: plugin => EditorView.scrollMargins.of(view => {
8459 let value = view.plugin(plugin);
8460 return value && { top: value.top.scrollMargin(), bottom: value.bottom.scrollMargin() };
8461 })
8462});
8463class PanelGroup {
8464 constructor(view, top, container) {
8465 this.view = view;
8466 this.top = top;
8467 this.container = container;
8468 this.dom = undefined;
8469 this.classes = "";
8470 this.panels = [];
8471 this.syncClasses();
8472 }
8473 sync(panels) {
8474 for (let p of this.panels)
8475 if (p.destroy && panels.indexOf(p) < 0)
8476 p.destroy();
8477 this.panels = panels;
8478 this.syncDOM();
8479 }
8480 syncDOM() {
8481 if (this.panels.length == 0) {
8482 if (this.dom) {
8483 this.dom.remove();
8484 this.dom = undefined;
8485 }
8486 return;
8487 }
8488 if (!this.dom) {
8489 this.dom = document.createElement("div");
8490 this.dom.className = this.top ? "cm-panels cm-panels-top" : "cm-panels cm-panels-bottom";
8491 this.dom.style[this.top ? "top" : "bottom"] = "0";
8492 let parent = this.container || this.view.dom;
8493 parent.insertBefore(this.dom, this.top ? parent.firstChild : null);
8494 }
8495 let curDOM = this.dom.firstChild;
8496 for (let panel of this.panels) {
8497 if (panel.dom.parentNode == this.dom) {
8498 while (curDOM != panel.dom)
8499 curDOM = rm(curDOM);
8500 curDOM = curDOM.nextSibling;
8501 }
8502 else {
8503 this.dom.insertBefore(panel.dom, curDOM);
8504 }
8505 }
8506 while (curDOM)
8507 curDOM = rm(curDOM);
8508 }
8509 scrollMargin() {
8510 return !this.dom || this.container ? 0
8511 : Math.max(0, this.top ?
8512 this.dom.getBoundingClientRect().bottom - Math.max(0, this.view.scrollDOM.getBoundingClientRect().top) :
8513 Math.min(innerHeight, this.view.scrollDOM.getBoundingClientRect().bottom) - this.dom.getBoundingClientRect().top);
8514 }
8515 syncClasses() {
8516 if (!this.container || this.classes == this.view.themeClasses)
8517 return;
8518 for (let cls of this.classes.split(" "))
8519 if (cls)
8520 this.container.classList.remove(cls);
8521 for (let cls of (this.classes = this.view.themeClasses).split(" "))
8522 if (cls)
8523 this.container.classList.add(cls);
8524 }
8525}
8526function rm(node) {
8527 let next = node.nextSibling;
8528 node.remove();
8529 return next;
8530}
8531/**
8532Opening a panel is done by providing a constructor function for
8533the panel through this facet. (The panel is closed again when its
8534constructor is no longer provided.) Values of `null` are ignored.
8535*/
8536const showPanel = /*@__PURE__*/Facet.define({
8537 enables: panelPlugin
8538});
8539
8540/**
8541A gutter marker represents a bit of information attached to a line
8542in a specific gutter. Your own custom markers have to extend this
8543class.
8544*/
8545class GutterMarker extends RangeValue {
8546 /**
8547 @internal
8548 */
8549 compare(other) {
8550 return this == other || this.constructor == other.constructor && this.eq(other);
8551 }
8552 /**
8553 Compare this marker to another marker of the same type.
8554 */
8555 eq(other) { return false; }
8556 /**
8557 Called if the marker has a `toDOM` method and its representation
8558 was removed from a gutter.
8559 */
8560 destroy(dom) { }
8561}
8562GutterMarker.prototype.elementClass = "";
8563GutterMarker.prototype.toDOM = undefined;
8564GutterMarker.prototype.mapMode = MapMode.TrackBefore;
8565GutterMarker.prototype.startSide = GutterMarker.prototype.endSide = -1;
8566GutterMarker.prototype.point = true;
8567/**
8568Facet used to add a class to all gutter elements for a given line.
8569Markers given to this facet should _only_ define an
8570[`elementclass`](https://codemirror.net/6/docs/ref/#view.GutterMarker.elementClass), not a
8571[`toDOM`](https://codemirror.net/6/docs/ref/#view.GutterMarker.toDOM) (or the marker will appear
8572in all gutters for the line).
8573*/
8574const gutterLineClass = /*@__PURE__*/Facet.define();
8575const defaults = {
8576 class: "",
8577 renderEmptyElements: false,
8578 elementStyle: "",
8579 markers: () => RangeSet.empty,
8580 lineMarker: () => null,
8581 lineMarkerChange: null,
8582 initialSpacer: null,
8583 updateSpacer: null,
8584 domEventHandlers: {}
8585};
8586const activeGutters = /*@__PURE__*/Facet.define();
8587/**
8588Define an editor gutter. The order in which the gutters appear is
8589determined by their extension priority.
8590*/
8591function gutter(config) {
8592 return [gutters(), activeGutters.of(Object.assign(Object.assign({}, defaults), config))];
8593}
8594const unfixGutters = /*@__PURE__*/Facet.define({
8595 combine: values => values.some(x => x)
8596});
8597/**
8598The gutter-drawing plugin is automatically enabled when you add a
8599gutter, but you can use this function to explicitly configure it.
8600
8601Unless `fixed` is explicitly set to `false`, the gutters are
8602fixed, meaning they don't scroll along with the content
8603horizontally (except on Internet Explorer, which doesn't support
8604CSS [`position:
8605sticky`](https://developer.mozilla.org/en-US/docs/Web/CSS/position#sticky)).
8606*/
8607function gutters(config) {
8608 let result = [
8609 gutterView,
8610 ];
8611 if (config && config.fixed === false)
8612 result.push(unfixGutters.of(true));
8613 return result;
8614}
8615const gutterView = /*@__PURE__*/ViewPlugin.fromClass(class {
8616 constructor(view) {
8617 this.view = view;
8618 this.prevViewport = view.viewport;
8619 this.dom = document.createElement("div");
8620 this.dom.className = "cm-gutters";
8621 this.dom.setAttribute("aria-hidden", "true");
8622 this.dom.style.minHeight = this.view.contentHeight + "px";
8623 this.gutters = view.state.facet(activeGutters).map(conf => new SingleGutterView(view, conf));
8624 for (let gutter of this.gutters)
8625 this.dom.appendChild(gutter.dom);
8626 this.fixed = !view.state.facet(unfixGutters);
8627 if (this.fixed) {
8628 // FIXME IE11 fallback, which doesn't support position: sticky,
8629 // by using position: relative + event handlers that realign the
8630 // gutter (or just force fixed=false on IE11?)
8631 this.dom.style.position = "sticky";
8632 }
8633 this.syncGutters(false);
8634 view.scrollDOM.insertBefore(this.dom, view.contentDOM);
8635 }
8636 update(update) {
8637 if (this.updateGutters(update)) {
8638 // Detach during sync when the viewport changed significantly
8639 // (such as during scrolling), since for large updates that is
8640 // faster.
8641 let vpA = this.prevViewport, vpB = update.view.viewport;
8642 let vpOverlap = Math.min(vpA.to, vpB.to) - Math.max(vpA.from, vpB.from);
8643 this.syncGutters(vpOverlap < (vpB.to - vpB.from) * 0.8);
8644 }
8645 if (update.geometryChanged)
8646 this.dom.style.minHeight = this.view.contentHeight + "px";
8647 if (this.view.state.facet(unfixGutters) != !this.fixed) {
8648 this.fixed = !this.fixed;
8649 this.dom.style.position = this.fixed ? "sticky" : "";
8650 }
8651 this.prevViewport = update.view.viewport;
8652 }
8653 syncGutters(detach) {
8654 let after = this.dom.nextSibling;
8655 if (detach)
8656 this.dom.remove();
8657 let lineClasses = RangeSet.iter(this.view.state.facet(gutterLineClass), this.view.viewport.from);
8658 let classSet = [];
8659 let contexts = this.gutters.map(gutter => new UpdateContext(gutter, this.view.viewport, -this.view.documentPadding.top));
8660 for (let line of this.view.viewportLineBlocks) {
8661 let text;
8662 if (Array.isArray(line.type)) {
8663 for (let b of line.type)
8664 if (b.type == BlockType.Text) {
8665 text = b;
8666 break;
8667 }
8668 }
8669 else {
8670 text = line.type == BlockType.Text ? line : undefined;
8671 }
8672 if (!text)
8673 continue;
8674 if (classSet.length)
8675 classSet = [];
8676 advanceCursor(lineClasses, classSet, line.from);
8677 for (let cx of contexts)
8678 cx.line(this.view, text, classSet);
8679 }
8680 for (let cx of contexts)
8681 cx.finish();
8682 if (detach)
8683 this.view.scrollDOM.insertBefore(this.dom, after);
8684 }
8685 updateGutters(update) {
8686 let prev = update.startState.facet(activeGutters), cur = update.state.facet(activeGutters);
8687 let change = update.docChanged || update.heightChanged || update.viewportChanged ||
8688 !RangeSet.eq(update.startState.facet(gutterLineClass), update.state.facet(gutterLineClass), update.view.viewport.from, update.view.viewport.to);
8689 if (prev == cur) {
8690 for (let gutter of this.gutters)
8691 if (gutter.update(update))
8692 change = true;
8693 }
8694 else {
8695 change = true;
8696 let gutters = [];
8697 for (let conf of cur) {
8698 let known = prev.indexOf(conf);
8699 if (known < 0) {
8700 gutters.push(new SingleGutterView(this.view, conf));
8701 }
8702 else {
8703 this.gutters[known].update(update);
8704 gutters.push(this.gutters[known]);
8705 }
8706 }
8707 for (let g of this.gutters) {
8708 g.dom.remove();
8709 if (gutters.indexOf(g) < 0)
8710 g.destroy();
8711 }
8712 for (let g of gutters)
8713 this.dom.appendChild(g.dom);
8714 this.gutters = gutters;
8715 }
8716 return change;
8717 }
8718 destroy() {
8719 for (let view of this.gutters)
8720 view.destroy();
8721 this.dom.remove();
8722 }
8723}, {
8724 provide: plugin => EditorView.scrollMargins.of(view => {
8725 let value = view.plugin(plugin);
8726 if (!value || value.gutters.length == 0 || !value.fixed)
8727 return null;
8728 return view.textDirection == Direction.LTR ? { left: value.dom.offsetWidth } : { right: value.dom.offsetWidth };
8729 })
8730});
8731function asArray(val) { return (Array.isArray(val) ? val : [val]); }
8732function advanceCursor(cursor, collect, pos) {
8733 while (cursor.value && cursor.from <= pos) {
8734 if (cursor.from == pos)
8735 collect.push(cursor.value);
8736 cursor.next();
8737 }
8738}
8739class UpdateContext {
8740 constructor(gutter, viewport, height) {
8741 this.gutter = gutter;
8742 this.height = height;
8743 this.localMarkers = [];
8744 this.i = 0;
8745 this.cursor = RangeSet.iter(gutter.markers, viewport.from);
8746 }
8747 line(view, line, extraMarkers) {
8748 if (this.localMarkers.length)
8749 this.localMarkers = [];
8750 advanceCursor(this.cursor, this.localMarkers, line.from);
8751 let localMarkers = extraMarkers.length ? this.localMarkers.concat(extraMarkers) : this.localMarkers;
8752 let forLine = this.gutter.config.lineMarker(view, line, localMarkers);
8753 if (forLine)
8754 localMarkers.unshift(forLine);
8755 let gutter = this.gutter;
8756 if (localMarkers.length == 0 && !gutter.config.renderEmptyElements)
8757 return;
8758 let above = line.top - this.height;
8759 if (this.i == gutter.elements.length) {
8760 let newElt = new GutterElement(view, line.height, above, localMarkers);
8761 gutter.elements.push(newElt);
8762 gutter.dom.appendChild(newElt.dom);
8763 }
8764 else {
8765 gutter.elements[this.i].update(view, line.height, above, localMarkers);
8766 }
8767 this.height = line.bottom;
8768 this.i++;
8769 }
8770 finish() {
8771 let gutter = this.gutter;
8772 while (gutter.elements.length > this.i) {
8773 let last = gutter.elements.pop();
8774 gutter.dom.removeChild(last.dom);
8775 last.destroy();
8776 }
8777 }
8778}
8779class SingleGutterView {
8780 constructor(view, config) {
8781 this.view = view;
8782 this.config = config;
8783 this.elements = [];
8784 this.spacer = null;
8785 this.dom = document.createElement("div");
8786 this.dom.className = "cm-gutter" + (this.config.class ? " " + this.config.class : "");
8787 for (let prop in config.domEventHandlers) {
8788 this.dom.addEventListener(prop, (event) => {
8789 let line = view.lineBlockAtHeight(event.clientY - view.documentTop);
8790 if (config.domEventHandlers[prop](view, line, event))
8791 event.preventDefault();
8792 });
8793 }
8794 this.markers = asArray(config.markers(view));
8795 if (config.initialSpacer) {
8796 this.spacer = new GutterElement(view, 0, 0, [config.initialSpacer(view)]);
8797 this.dom.appendChild(this.spacer.dom);
8798 this.spacer.dom.style.cssText += "visibility: hidden; pointer-events: none";
8799 }
8800 }
8801 update(update) {
8802 let prevMarkers = this.markers;
8803 this.markers = asArray(this.config.markers(update.view));
8804 if (this.spacer && this.config.updateSpacer) {
8805 let updated = this.config.updateSpacer(this.spacer.markers[0], update);
8806 if (updated != this.spacer.markers[0])
8807 this.spacer.update(update.view, 0, 0, [updated]);
8808 }
8809 let vp = update.view.viewport;
8810 return !RangeSet.eq(this.markers, prevMarkers, vp.from, vp.to) ||
8811 (this.config.lineMarkerChange ? this.config.lineMarkerChange(update) : false);
8812 }
8813 destroy() {
8814 for (let elt of this.elements)
8815 elt.destroy();
8816 }
8817}
8818class GutterElement {
8819 constructor(view, height, above, markers) {
8820 this.height = -1;
8821 this.above = 0;
8822 this.markers = [];
8823 this.dom = document.createElement("div");
8824 this.dom.className = "cm-gutterElement";
8825 this.update(view, height, above, markers);
8826 }
8827 update(view, height, above, markers) {
8828 if (this.height != height)
8829 this.dom.style.height = (this.height = height) + "px";
8830 if (this.above != above)
8831 this.dom.style.marginTop = (this.above = above) ? above + "px" : "";
8832 if (!sameMarkers(this.markers, markers))
8833 this.setMarkers(view, markers);
8834 }
8835 setMarkers(view, markers) {
8836 let cls = "cm-gutterElement", domPos = this.dom.firstChild;
8837 for (let iNew = 0, iOld = 0;;) {
8838 let skipTo = iOld, marker = iNew < markers.length ? markers[iNew++] : null, matched = false;
8839 if (marker) {
8840 let c = marker.elementClass;
8841 if (c)
8842 cls += " " + c;
8843 for (let i = iOld; i < this.markers.length; i++)
8844 if (this.markers[i].compare(marker)) {
8845 skipTo = i;
8846 matched = true;
8847 break;
8848 }
8849 }
8850 else {
8851 skipTo = this.markers.length;
8852 }
8853 while (iOld < skipTo) {
8854 let next = this.markers[iOld++];
8855 if (next.toDOM) {
8856 next.destroy(domPos);
8857 let after = domPos.nextSibling;
8858 domPos.remove();
8859 domPos = after;
8860 }
8861 }
8862 if (!marker)
8863 break;
8864 if (marker.toDOM) {
8865 if (matched)
8866 domPos = domPos.nextSibling;
8867 else
8868 this.dom.insertBefore(marker.toDOM(view), domPos);
8869 }
8870 if (matched)
8871 iOld++;
8872 }
8873 this.dom.className = cls;
8874 this.markers = markers;
8875 }
8876 destroy() {
8877 this.setMarkers(null, []); // First argument not used unless creating markers
8878 }
8879}
8880function sameMarkers(a, b) {
8881 if (a.length != b.length)
8882 return false;
8883 for (let i = 0; i < a.length; i++)
8884 if (!a[i].compare(b[i]))
8885 return false;
8886 return true;
8887}
8888/**
8889Facet used to provide markers to the line number gutter.
8890*/
8891const lineNumberMarkers = /*@__PURE__*/Facet.define();
8892const lineNumberConfig = /*@__PURE__*/Facet.define({
8893 combine(values) {
8894 return combineConfig(values, { formatNumber: String, domEventHandlers: {} }, {
8895 domEventHandlers(a, b) {
8896 let result = Object.assign({}, a);
8897 for (let event in b) {
8898 let exists = result[event], add = b[event];
8899 result[event] = exists ? (view, line, event) => exists(view, line, event) || add(view, line, event) : add;
8900 }
8901 return result;
8902 }
8903 });
8904 }
8905});
8906class NumberMarker extends GutterMarker {
8907 constructor(number) {
8908 super();
8909 this.number = number;
8910 }
8911 eq(other) { return this.number == other.number; }
8912 toDOM() { return document.createTextNode(this.number); }
8913}
8914function formatNumber(view, number) {
8915 return view.state.facet(lineNumberConfig).formatNumber(number, view.state);
8916}
8917const lineNumberGutter = /*@__PURE__*/activeGutters.compute([lineNumberConfig], state => ({
8918 class: "cm-lineNumbers",
8919 renderEmptyElements: false,
8920 markers(view) { return view.state.facet(lineNumberMarkers); },
8921 lineMarker(view, line, others) {
8922 if (others.some(m => m.toDOM))
8923 return null;
8924 return new NumberMarker(formatNumber(view, view.state.doc.lineAt(line.from).number));
8925 },
8926 lineMarkerChange: update => update.startState.facet(lineNumberConfig) != update.state.facet(lineNumberConfig),
8927 initialSpacer(view) {
8928 return new NumberMarker(formatNumber(view, maxLineNumber(view.state.doc.lines)));
8929 },
8930 updateSpacer(spacer, update) {
8931 let max = formatNumber(update.view, maxLineNumber(update.view.state.doc.lines));
8932 return max == spacer.number ? spacer : new NumberMarker(max);
8933 },
8934 domEventHandlers: state.facet(lineNumberConfig).domEventHandlers
8935}));
8936/**
8937Create a line number gutter extension.
8938*/
8939function lineNumbers(config = {}) {
8940 return [
8941 lineNumberConfig.of(config),
8942 gutters(),
8943 lineNumberGutter
8944 ];
8945}
8946function maxLineNumber(lines) {
8947 let last = 9;
8948 while (last < lines)
8949 last = last * 10 + 9;
8950 return last;
8951}
8952const activeLineGutterMarker = /*@__PURE__*/new class extends GutterMarker {
8953 constructor() {
8954 super(...arguments);
8955 this.elementClass = "cm-activeLineGutter";
8956 }
8957};
8958const activeLineGutterHighlighter = /*@__PURE__*/gutterLineClass.compute(["selection"], state => {
8959 let marks = [], last = -1;
8960 for (let range of state.selection.ranges)
8961 if (range.empty) {
8962 let linePos = state.doc.lineAt(range.head).from;
8963 if (linePos > last) {
8964 last = linePos;
8965 marks.push(activeLineGutterMarker.range(linePos));
8966 }
8967 }
8968 return RangeSet.of(marks);
8969});
8970/**
8971Returns an extension that adds a `cm-activeLineGutter` class to
8972all gutter elements on the [active
8973line](https://codemirror.net/6/docs/ref/#view.highlightActiveLine).
8974*/
8975function highlightActiveLineGutter() {
8976 return activeLineGutterHighlighter;
8977}
8978
8979/**
8980@internal
8981*/
8982const __test = { HeightMap, HeightOracle, MeasuredHeights, QueryType, ChangedRange, computeOrder, moveVisually };
8983
8984export { BidiSpan, BlockInfo, BlockType, Decoration, Direction, EditorView, GutterMarker, MatchDecorator, ViewPlugin, ViewUpdate, WidgetType, __test, closeHoverTooltips, crosshairCursor, drawSelection, dropCursor, getPanel, getTooltip, gutter, gutterLineClass, gutters, hasHoverTooltips, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, hoverTooltip, keymap, lineNumberMarkers, lineNumbers, logException, panels, placeholder, rectangularSelection, repositionTooltips, runScopeHandlers, scrollPastEnd, showPanel, showTooltip, tooltips };