@codemirror/view 0.19.25 → 0.19.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -270,7 +270,7 @@ class DOMPos {
270
270
  static before(dom, precise) { return new DOMPos(dom.parentNode, domIndex(dom), precise); }
271
271
  static after(dom, precise) { return new DOMPos(dom.parentNode, domIndex(dom) + 1, precise); }
272
272
  }
273
- const none$3 = [];
273
+ const noChildren = [];
274
274
  class ContentView {
275
275
  constructor() {
276
276
  this.parent = null;
@@ -436,12 +436,12 @@ class ContentView {
436
436
  v = parent;
437
437
  }
438
438
  }
439
- replaceChildren(from, to, children = none$3) {
439
+ replaceChildren(from, to, children = noChildren) {
440
440
  this.markDirty();
441
441
  for (let i = from; i < to; i++) {
442
442
  let child = this.children[i];
443
443
  if (child.parent == this)
444
- child.parent = null;
444
+ child.destroy();
445
445
  }
446
446
  this.children.splice(from, to - from, ...children);
447
447
  for (let i = 0; i < children.length; i++)
@@ -463,6 +463,17 @@ class ContentView {
463
463
  }
464
464
  static get(node) { return node.cmView; }
465
465
  get isEditable() { return true; }
466
+ merge(from, to, source, hasStart, openStart, openEnd) {
467
+ return false;
468
+ }
469
+ become(other) { return false; }
470
+ // When this is a zero-length view with a side, this should return a
471
+ // number <= 0 to indicate it is before its position, or a
472
+ // number > 0 when after its position.
473
+ getSide() { return 0; }
474
+ destroy() {
475
+ this.parent = null;
476
+ }
466
477
  }
467
478
  ContentView.prototype.breakAfter = 0;
468
479
  // Remove a DOM node and return its next sibling.
@@ -490,6 +501,94 @@ class ChildCursor {
490
501
  }
491
502
  }
492
503
  }
504
+ function replaceRange(parent, fromI, fromOff, toI, toOff, insert, breakAtStart, openStart, openEnd) {
505
+ let { children } = parent;
506
+ let before = children.length ? children[fromI] : null;
507
+ let last = insert.length ? insert[insert.length - 1] : null;
508
+ let breakAtEnd = last ? last.breakAfter : breakAtStart;
509
+ // Change within a single child
510
+ if (fromI == toI && before && !breakAtStart && !breakAtEnd && insert.length < 2 &&
511
+ before.merge(fromOff, toOff, insert.length ? last : null, fromOff == 0, openStart, openEnd))
512
+ return;
513
+ if (toI < children.length) {
514
+ let after = children[toI];
515
+ // Make sure the end of the child after the update is preserved in `after`
516
+ if (after && toOff < after.length) {
517
+ // If we're splitting a child, separate part of it to avoid that
518
+ // being mangled when updating the child before the update.
519
+ if (fromI == toI) {
520
+ after = after.split(toOff);
521
+ toOff = 0;
522
+ }
523
+ // If the element after the replacement should be merged with
524
+ // the last replacing element, update `content`
525
+ if (!breakAtEnd && last && after.merge(0, toOff, last, true, 0, openEnd)) {
526
+ insert[insert.length - 1] = after;
527
+ }
528
+ else {
529
+ // Remove the start of the after element, if necessary, and
530
+ // add it to `content`.
531
+ if (toOff)
532
+ after.merge(0, toOff, null, false, 0, openEnd);
533
+ insert.push(after);
534
+ }
535
+ }
536
+ else if (after === null || after === void 0 ? void 0 : after.breakAfter) {
537
+ // The element at `toI` is entirely covered by this range.
538
+ // Preserve its line break, if any.
539
+ if (last)
540
+ last.breakAfter = 1;
541
+ else
542
+ breakAtStart = 1;
543
+ }
544
+ // Since we've handled the next element from the current elements
545
+ // now, make sure `toI` points after that.
546
+ toI++;
547
+ }
548
+ if (before) {
549
+ before.breakAfter = breakAtStart;
550
+ if (fromOff > 0) {
551
+ if (!breakAtStart && insert.length && before.merge(fromOff, before.length, insert[0], false, openStart, 0)) {
552
+ before.breakAfter = insert.shift().breakAfter;
553
+ }
554
+ else if (fromOff < before.length || before.children.length && before.children[before.children.length - 1].length == 0) {
555
+ before.merge(fromOff, before.length, null, false, openStart, 0);
556
+ }
557
+ fromI++;
558
+ }
559
+ }
560
+ // Try to merge widgets on the boundaries of the replacement
561
+ while (fromI < toI && insert.length) {
562
+ if (children[toI - 1].become(insert[insert.length - 1])) {
563
+ toI--;
564
+ insert.pop();
565
+ openEnd = insert.length ? 0 : openStart;
566
+ }
567
+ else if (children[fromI].become(insert[0])) {
568
+ fromI++;
569
+ insert.shift();
570
+ openStart = insert.length ? 0 : openEnd;
571
+ }
572
+ else {
573
+ break;
574
+ }
575
+ }
576
+ if (!insert.length && fromI && toI < children.length && !children[fromI - 1].breakAfter &&
577
+ children[toI].merge(0, 0, children[fromI - 1], false, openStart, openEnd))
578
+ fromI--;
579
+ if (fromI < toI || insert.length)
580
+ parent.replaceChildren(fromI, toI, insert);
581
+ }
582
+ function mergeChildrenInto(parent, from, to, insert, openStart, openEnd) {
583
+ let cur = parent.childCursor();
584
+ let { i: toI, off: toOff } = cur.findPos(to, 1);
585
+ let { i: fromI, off: fromOff } = cur.findPos(from, -1);
586
+ let dLen = from - to;
587
+ for (let view of insert)
588
+ dLen += view.length;
589
+ parent.length += dLen;
590
+ replaceRange(parent, fromI, fromOff, toI, toOff, insert, 0, openStart, openEnd);
591
+ }
493
592
 
494
593
  let [nav, doc] = typeof navigator != "undefined"
495
594
  ? [navigator, document]
@@ -521,21 +620,8 @@ var browser = {
521
620
  tabSize: doc.documentElement.style.tabSize != null ? "tab-size" : "-moz-tab-size"
522
621
  };
523
622
 
524
- const none$2 = [];
525
- class InlineView extends ContentView {
526
- /**
527
- Return true when this view is equivalent to `other` and can take
528
- on its role.
529
- */
530
- become(_other) { return false; }
531
- // When this is a zero-length view with a side, this should return a
532
- // negative number to indicate it is before its position, or a
533
- // positive number when after its position.
534
- getSide() { return 0; }
535
- }
536
- InlineView.prototype.children = none$2;
537
623
  const MaxJoinLen = 256;
538
- class TextView extends InlineView {
624
+ class TextView extends ContentView {
539
625
  constructor(text) {
540
626
  super();
541
627
  this.text = text;
@@ -566,7 +652,7 @@ class TextView extends InlineView {
566
652
  this.markDirty();
567
653
  return true;
568
654
  }
569
- slice(from) {
655
+ split(from) {
570
656
  let result = new TextView(this.text.slice(from));
571
657
  this.text = this.text.slice(0, from);
572
658
  return result;
@@ -582,7 +668,7 @@ class TextView extends InlineView {
582
668
  return textCoords(this.dom, pos, side);
583
669
  }
584
670
  }
585
- class MarkView extends InlineView {
671
+ class MarkView extends ContentView {
586
672
  constructor(mark, children = [], length = 0) {
587
673
  super();
588
674
  this.mark = mark;
@@ -605,20 +691,20 @@ class MarkView extends InlineView {
605
691
  this.createDOM();
606
692
  super.sync(track);
607
693
  }
608
- merge(from, to, source, openStart, openEnd) {
694
+ merge(from, to, source, _hasStart, openStart, openEnd) {
609
695
  if (source && (!(source instanceof MarkView && source.mark.eq(this.mark)) ||
610
696
  (from && openStart <= 0) || (to < this.length && openEnd <= 0)))
611
697
  return false;
612
- mergeInlineChildren(this, from, to, source ? source.children : none$2, openStart - 1, openEnd - 1);
698
+ mergeChildrenInto(this, from, to, source ? source.children : [], openStart - 1, openEnd - 1);
613
699
  this.markDirty();
614
700
  return true;
615
701
  }
616
- slice(from) {
702
+ split(from) {
617
703
  let result = [], off = 0, detachFrom = -1, i = 0;
618
704
  for (let elt of this.children) {
619
705
  let end = off + elt.length;
620
706
  if (end > from)
621
- result.push(off < from ? elt.slice(from - off) : elt);
707
+ result.push(off < from ? elt.split(from - off) : elt);
622
708
  if (detachFrom < 0 && off >= from)
623
709
  detachFrom = i;
624
710
  off = end;
@@ -626,8 +712,10 @@ class MarkView extends InlineView {
626
712
  }
627
713
  let length = this.length - from;
628
714
  this.length = from;
629
- if (detachFrom > -1)
630
- this.replaceChildren(detachFrom, this.children.length);
715
+ if (detachFrom > -1) {
716
+ this.children.length = detachFrom;
717
+ this.markDirty();
718
+ }
631
719
  return new MarkView(this.mark, result, length);
632
720
  }
633
721
  domAtPos(pos) {
@@ -669,7 +757,7 @@ function textCoords(text, pos, side) {
669
757
  return flatten ? flattenRect(rect, flatten < 0) : rect || null;
670
758
  }
671
759
  // Also used for collapsed ranges that don't have a placeholder widget!
672
- class WidgetView extends InlineView {
760
+ class WidgetView extends ContentView {
673
761
  constructor(widget, length, side) {
674
762
  super();
675
763
  this.widget = widget;
@@ -679,7 +767,7 @@ class WidgetView extends InlineView {
679
767
  static create(widget, length, side) {
680
768
  return new (widget.customView || WidgetView)(widget, length, side);
681
769
  }
682
- slice(from) {
770
+ split(from) {
683
771
  let result = WidgetView.create(this.widget, this.length - from, this.side);
684
772
  this.length -= from;
685
773
  return result;
@@ -691,7 +779,7 @@ class WidgetView extends InlineView {
691
779
  }
692
780
  }
693
781
  getSide() { return this.side; }
694
- merge(from, to, source, openStart, openEnd) {
782
+ merge(from, to, source, hasStart, openStart, openEnd) {
695
783
  if (source && (!(source instanceof WidgetView) || !this.widget.compare(source.widget) ||
696
784
  from > 0 && openStart <= 0 || to < this.length && openEnd <= 0))
697
785
  return false;
@@ -736,6 +824,11 @@ class WidgetView extends InlineView {
736
824
  return (pos == 0 && side > 0 || pos == this.length && side <= 0) ? rect : flattenRect(rect, pos == 0);
737
825
  }
738
826
  get isEditable() { return false; }
827
+ destroy() {
828
+ super.destroy();
829
+ if (this.dom)
830
+ this.widget.destroy(this.dom);
831
+ }
739
832
  }
740
833
  class CompositionView extends WidgetView {
741
834
  domAtPos(pos) { return new DOMPos(this.widget.text, pos); }
@@ -749,10 +842,13 @@ class CompositionView extends WidgetView {
749
842
  coordsAt(pos, side) { return textCoords(this.widget.text, pos, side); }
750
843
  get isEditable() { return true; }
751
844
  }
845
+ // Use two characters on Android, to prevent Chrome from closing the
846
+ // virtual keyboard when backspacing after a widget (#602).
847
+ const ZeroWidthSpace = browser.android ? "\u200b\u200b" : "\u200b";
752
848
  // These are drawn around uneditable widgets to avoid a number of
753
849
  // browser bugs that show up when the cursor is directly next to
754
850
  // uneditable inline content.
755
- class WidgetBufferView extends InlineView {
851
+ class WidgetBufferView extends ContentView {
756
852
  constructor(side) {
757
853
  super();
758
854
  this.side = side;
@@ -762,15 +858,16 @@ class WidgetBufferView extends InlineView {
762
858
  become(other) {
763
859
  return other instanceof WidgetBufferView && other.side == this.side;
764
860
  }
765
- slice() { return new WidgetBufferView(this.side); }
861
+ split() { return new WidgetBufferView(this.side); }
766
862
  sync() {
767
863
  if (!this.dom)
768
- this.setDOM(document.createTextNode("\u200b"));
769
- else if (this.dirty && this.dom.nodeValue != "\u200b")
770
- this.dom.nodeValue = "\u200b";
864
+ this.setDOM(document.createTextNode(ZeroWidthSpace));
865
+ else if (this.dirty && this.dom.nodeValue != ZeroWidthSpace)
866
+ this.dom.nodeValue = ZeroWidthSpace;
771
867
  }
772
868
  getSide() { return this.side; }
773
869
  domAtPos(pos) { return DOMPos.before(this.dom); }
870
+ localPosFromDOM() { return 0; }
774
871
  domBoundsAround() { return null; }
775
872
  coordsAt(pos) {
776
873
  let rects = clientRectsFor(this.dom);
@@ -780,90 +877,7 @@ class WidgetBufferView extends InlineView {
780
877
  return Text.of([this.dom.nodeValue.replace(/\u200b/g, "")]);
781
878
  }
782
879
  }
783
- function mergeInlineChildren(parent, from, to, elts, openStart, openEnd) {
784
- let cur = parent.childCursor();
785
- let { i: toI, off: toOff } = cur.findPos(to, 1);
786
- let { i: fromI, off: fromOff } = cur.findPos(from, -1);
787
- let dLen = from - to;
788
- for (let view of elts)
789
- dLen += view.length;
790
- parent.length += dLen;
791
- let { children } = parent;
792
- // Both from and to point into the same child view
793
- if (fromI == toI && fromOff) {
794
- let start = children[fromI];
795
- // Maybe just update that view and be done
796
- if (elts.length == 1 && start.merge(fromOff, toOff, elts[0], openStart, openEnd))
797
- return;
798
- if (elts.length == 0) {
799
- start.merge(fromOff, toOff, null, openStart, openEnd);
800
- return;
801
- }
802
- // Otherwise split it, so that we don't have to worry about aliasing front/end afterwards
803
- let after = start.slice(toOff);
804
- if (after.merge(0, 0, elts[elts.length - 1], 0, openEnd))
805
- elts[elts.length - 1] = after;
806
- else
807
- elts.push(after);
808
- toI++;
809
- openEnd = toOff = 0;
810
- }
811
- // Make sure start and end positions fall on node boundaries
812
- // (fromOff/toOff are no longer used after this), and that if the
813
- // start or end of the elts can be merged with adjacent nodes,
814
- // this is done
815
- if (toOff) {
816
- let end = children[toI];
817
- if (elts.length && end.merge(0, toOff, elts[elts.length - 1], 0, openEnd)) {
818
- elts.pop();
819
- openEnd = elts.length ? 0 : openStart;
820
- }
821
- else {
822
- end.merge(0, toOff, null, 0, 0);
823
- }
824
- }
825
- else if (toI < children.length && elts.length &&
826
- children[toI].merge(0, 0, elts[elts.length - 1], 0, openEnd)) {
827
- elts.pop();
828
- openEnd = elts.length ? 0 : openStart;
829
- }
830
- if (fromOff) {
831
- let start = children[fromI];
832
- if (elts.length && start.merge(fromOff, start.length, elts[0], openStart, 0)) {
833
- elts.shift();
834
- openStart = elts.length ? 0 : openEnd;
835
- }
836
- else {
837
- start.merge(fromOff, start.length, null, 0, 0);
838
- }
839
- fromI++;
840
- }
841
- else if (fromI && elts.length) {
842
- let end = children[fromI - 1];
843
- if (end.merge(end.length, end.length, elts[0], openStart, 0)) {
844
- elts.shift();
845
- openStart = elts.length ? 0 : openEnd;
846
- }
847
- }
848
- // Then try to merge any mergeable nodes at the start and end of
849
- // the changed range
850
- while (fromI < toI && elts.length && children[toI - 1].become(elts[elts.length - 1])) {
851
- elts.pop();
852
- toI--;
853
- openEnd = elts.length ? 0 : openStart;
854
- }
855
- while (fromI < toI && elts.length && children[fromI].become(elts[0])) {
856
- elts.shift();
857
- fromI++;
858
- openStart = elts.length ? 0 : openEnd;
859
- }
860
- if (!elts.length && fromI && toI < children.length &&
861
- children[toI].merge(0, 0, children[fromI - 1], openStart, openEnd))
862
- fromI--;
863
- // And if anything remains, splice the child array to insert the new elts
864
- if (elts.length || fromI != toI)
865
- parent.replaceChildren(fromI, toI, elts);
866
- }
880
+ TextView.prototype.children = WidgetView.prototype.children = WidgetBufferView.prototype.children = noChildren;
867
881
  function inlineDOMAtPos(dom, children, pos) {
868
882
  let i = 0;
869
883
  for (let off = 0; i < children.length; i++) {
@@ -1004,6 +1018,11 @@ class WidgetType {
1004
1018
  @internal
1005
1019
  */
1006
1020
  get customView() { return null; }
1021
+ /**
1022
+ This is called when the an instance of the widget is removed
1023
+ from the editor view.
1024
+ */
1025
+ destroy(_dom) { }
1007
1026
  }
1008
1027
  /**
1009
1028
  The different types of blocks that can occur in an editor view.
@@ -1163,12 +1182,12 @@ class PointDecoration extends Decoration {
1163
1182
  super(startSide, endSide, widget, spec);
1164
1183
  this.block = block;
1165
1184
  this.isReplace = isReplace;
1166
- this.mapMode = !block ? MapMode.TrackDel : startSide < 0 ? MapMode.TrackBefore : MapMode.TrackAfter;
1185
+ this.mapMode = !block ? MapMode.TrackDel : startSide <= 0 ? MapMode.TrackBefore : MapMode.TrackAfter;
1167
1186
  }
1168
1187
  // Only relevant when this.block == true
1169
1188
  get type() {
1170
1189
  return this.startSide < this.endSide ? BlockType.WidgetRange
1171
- : this.startSide < 0 ? BlockType.WidgetBefore : BlockType.WidgetAfter;
1190
+ : this.startSide <= 0 ? BlockType.WidgetBefore : BlockType.WidgetAfter;
1172
1191
  }
1173
1192
  get heightRelevant() { return this.block || !!this.widget && this.widget.estimatedHeight >= 5; }
1174
1193
  eq(other) {
@@ -1178,7 +1197,7 @@ class PointDecoration extends Decoration {
1178
1197
  this.startSide == other.startSide && this.endSide == other.endSide;
1179
1198
  }
1180
1199
  range(from, to = from) {
1181
- if (this.isReplace && (from > to || (from == to && this.startSide > 0 && this.endSide < 0)))
1200
+ if (this.isReplace && (from > to || (from == to && this.startSide > 0 && this.endSide <= 0)))
1182
1201
  throw new RangeError("Invalid range for replacement decoration");
1183
1202
  if (!this.isReplace && to != from)
1184
1203
  throw new RangeError("Widget decorations can only have zero-length ranges");
@@ -1215,16 +1234,16 @@ class LineView extends ContentView {
1215
1234
  this.breakAfter = 0;
1216
1235
  }
1217
1236
  // Consumes source
1218
- merge(from, to, source, takeDeco, openStart, openEnd) {
1237
+ merge(from, to, source, hasStart, openStart, openEnd) {
1219
1238
  if (source) {
1220
1239
  if (!(source instanceof LineView))
1221
1240
  return false;
1222
1241
  if (!this.dom)
1223
1242
  source.transferDOM(this); // Reuse source.dom when appropriate
1224
1243
  }
1225
- if (takeDeco)
1244
+ if (hasStart)
1226
1245
  this.setDeco(source ? source.attrs : null);
1227
- mergeInlineChildren(this, from, to, source ? source.children : none$1, openStart, openEnd);
1246
+ mergeChildrenInto(this, from, to, source ? source.children : [], openStart, openEnd);
1228
1247
  return true;
1229
1248
  }
1230
1249
  split(at) {
@@ -1234,16 +1253,14 @@ class LineView extends ContentView {
1234
1253
  return end;
1235
1254
  let { i, off } = this.childPos(at);
1236
1255
  if (off) {
1237
- end.append(this.children[i].slice(off), 0);
1238
- this.children[i].merge(off, this.children[i].length, null, 0, 0);
1256
+ end.append(this.children[i].split(off), 0);
1257
+ this.children[i].merge(off, this.children[i].length, null, false, 0, 0);
1239
1258
  i++;
1240
1259
  }
1241
1260
  for (let j = i; j < this.children.length; j++)
1242
1261
  end.append(this.children[j], 0);
1243
- while (i > 0 && this.children[i - 1].length == 0) {
1244
- this.children[i - 1].parent = null;
1245
- i--;
1246
- }
1262
+ while (i > 0 && this.children[i - 1].length == 0)
1263
+ this.children[--i].destroy();
1247
1264
  this.children.length = i;
1248
1265
  this.markDirty();
1249
1266
  this.length = at;
@@ -1266,7 +1283,6 @@ class LineView extends ContentView {
1266
1283
  this.attrs = attrs;
1267
1284
  }
1268
1285
  }
1269
- // Only called when building a line view in ContentBuilder
1270
1286
  append(child, openStart) {
1271
1287
  joinInlineInto(this, child, openStart);
1272
1288
  }
@@ -1323,7 +1339,7 @@ class LineView extends ContentView {
1323
1339
  coordsAt(pos, side) {
1324
1340
  return coordsInChildren(this, pos, side);
1325
1341
  }
1326
- match(_other) { return false; }
1342
+ become(_other) { return false; }
1327
1343
  get type() { return BlockType.Text; }
1328
1344
  static find(docView, pos) {
1329
1345
  for (let i = 0, off = 0;; i++) {
@@ -1338,7 +1354,6 @@ class LineView extends ContentView {
1338
1354
  }
1339
1355
  }
1340
1356
  }
1341
- const none$1 = [];
1342
1357
  class BlockWidgetView extends ContentView {
1343
1358
  constructor(widget, length, type) {
1344
1359
  super();
@@ -1364,7 +1379,7 @@ class BlockWidgetView extends ContentView {
1364
1379
  end.breakAfter = this.breakAfter;
1365
1380
  return end;
1366
1381
  }
1367
- get children() { return none$1; }
1382
+ get children() { return noChildren; }
1368
1383
  sync() {
1369
1384
  if (!this.dom || !this.widget.updateDOM(this.dom)) {
1370
1385
  this.setDOM(this.widget.toDOM(this.editorView));
@@ -1375,7 +1390,7 @@ class BlockWidgetView extends ContentView {
1375
1390
  return this.parent ? this.parent.view.state.doc.slice(this.posAtStart, this.posAtEnd) : Text$1.empty;
1376
1391
  }
1377
1392
  domBoundsAround() { return null; }
1378
- match(other) {
1393
+ become(other) {
1379
1394
  if (other instanceof BlockWidgetView && other.type == this.type &&
1380
1395
  other.widget.constructor == this.widget.constructor) {
1381
1396
  if (!other.widget.eq(this.widget))
@@ -1389,6 +1404,11 @@ class BlockWidgetView extends ContentView {
1389
1404
  }
1390
1405
  ignoreMutation() { return true; }
1391
1406
  ignoreEvent(event) { return this.widget.ignoreEvent(event); }
1407
+ destroy() {
1408
+ super.destroy();
1409
+ if (this.dom)
1410
+ this.widget.destroy(this.dom);
1411
+ }
1392
1412
  }
1393
1413
 
1394
1414
  class ContentBuilder {
@@ -1918,854 +1938,882 @@ class ViewUpdate {
1918
1938
  get empty() { return this.flags == 0 && this.transactions.length == 0; }
1919
1939
  }
1920
1940
 
1921
- class DocView extends ContentView {
1922
- constructor(view) {
1923
- super();
1924
- this.view = view;
1925
- this.compositionDeco = Decoration.none;
1926
- this.decorations = [];
1927
- // Track a minimum width for the editor. When measuring sizes in
1928
- // measureVisibleLineHeights, this is updated to point at the width
1929
- // of a given element and its extent in the document. When a change
1930
- // happens in that range, these are reset. That way, once we've seen
1931
- // a line/element of a given length, we keep the editor wide enough
1932
- // to fit at least that element, until it is changed, at which point
1933
- // we forget it again.
1934
- this.minWidth = 0;
1935
- this.minWidthFrom = 0;
1936
- this.minWidthTo = 0;
1937
- // Track whether the DOM selection was set in a lossy way, so that
1938
- // we don't mess it up when reading it back it
1939
- this.impreciseAnchor = null;
1940
- this.impreciseHead = null;
1941
- this.forceSelection = false;
1942
- // Used by the resize observer to ignore resizes that we caused
1943
- // ourselves
1944
- this.lastUpdate = Date.now();
1945
- this.setDOM(view.contentDOM);
1946
- this.children = [new LineView];
1947
- this.children[0].setParent(this);
1948
- this.updateInner([new ChangedRange(0, 0, 0, view.state.doc.length)], this.updateDeco(), 0);
1941
+ /**
1942
+ Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
1943
+ */
1944
+ var Direction = /*@__PURE__*/(function (Direction) {
1945
+ // (These are chosen to match the base levels, in bidi algorithm
1946
+ // terms, of spans in that direction.)
1947
+ /**
1948
+ Left-to-right.
1949
+ */
1950
+ Direction[Direction["LTR"] = 0] = "LTR";
1951
+ /**
1952
+ Right-to-left.
1953
+ */
1954
+ Direction[Direction["RTL"] = 1] = "RTL";
1955
+ return Direction})(Direction || (Direction = {}));
1956
+ const LTR = Direction.LTR, RTL = Direction.RTL;
1957
+ // Decode a string with each type encoded as log2(type)
1958
+ function dec(str) {
1959
+ let result = [];
1960
+ for (let i = 0; i < str.length; i++)
1961
+ result.push(1 << +str[i]);
1962
+ return result;
1963
+ }
1964
+ // Character types for codepoints 0 to 0xf8
1965
+ const LowTypes = /*@__PURE__*/dec("88888888888888888888888888888888888666888888787833333333337888888000000000000000000000000008888880000000000000000000000000088888888888888888888888888888888888887866668888088888663380888308888800000000000000000000000800000000000000000000000000000008");
1966
+ // Character types for codepoints 0x600 to 0x6f9
1967
+ const ArabicTypes = /*@__PURE__*/dec("4444448826627288999999999992222222222222222222222222222222222222222222222229999999999999999999994444444444644222822222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222999999949999999229989999223333333333");
1968
+ const Brackets = /*@__PURE__*/Object.create(null), BracketStack = [];
1969
+ // There's a lot more in
1970
+ // https://www.unicode.org/Public/UCD/latest/ucd/BidiBrackets.txt,
1971
+ // which are left out to keep code size down.
1972
+ for (let p of ["()", "[]", "{}"]) {
1973
+ let l = /*@__PURE__*/p.charCodeAt(0), r = /*@__PURE__*/p.charCodeAt(1);
1974
+ Brackets[l] = r;
1975
+ Brackets[r] = -l;
1976
+ }
1977
+ function charType(ch) {
1978
+ return ch <= 0xf7 ? LowTypes[ch] :
1979
+ 0x590 <= ch && ch <= 0x5f4 ? 2 /* R */ :
1980
+ 0x600 <= ch && ch <= 0x6f9 ? ArabicTypes[ch - 0x600] :
1981
+ 0x6ee <= ch && ch <= 0x8ac ? 4 /* AL */ :
1982
+ 0x2000 <= ch && ch <= 0x200b ? 256 /* NI */ :
1983
+ ch == 0x200c ? 256 /* NI */ : 1 /* L */;
1984
+ }
1985
+ const BidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
1986
+ /**
1987
+ Represents a contiguous range of text that has a single direction
1988
+ (as in left-to-right or right-to-left).
1989
+ */
1990
+ class BidiSpan {
1991
+ /**
1992
+ @internal
1993
+ */
1994
+ constructor(
1995
+ /**
1996
+ The start of the span (relative to the start of the line).
1997
+ */
1998
+ from,
1999
+ /**
2000
+ The end of the span.
2001
+ */
2002
+ to,
2003
+ /**
2004
+ The ["bidi
2005
+ level"](https://unicode.org/reports/tr9/#Basic_Display_Algorithm)
2006
+ of the span (in this context, 0 means
2007
+ left-to-right, 1 means right-to-left, 2 means left-to-right
2008
+ number inside right-to-left text).
2009
+ */
2010
+ level) {
2011
+ this.from = from;
2012
+ this.to = to;
2013
+ this.level = level;
1949
2014
  }
1950
- get root() { return this.view.root; }
1951
- get editorView() { return this.view; }
1952
- get length() { return this.view.state.doc.length; }
1953
- // Update the document view to a given state. scrollIntoView can be
1954
- // used as a hint to compute a new viewport that includes that
1955
- // position, if we know the editor is going to scroll that position
1956
- // into view.
1957
- update(update) {
1958
- let changedRanges = update.changedRanges;
1959
- if (this.minWidth > 0 && changedRanges.length) {
1960
- if (!changedRanges.every(({ fromA, toA }) => toA < this.minWidthFrom || fromA > this.minWidthTo)) {
1961
- this.minWidth = 0;
1962
- }
1963
- else {
1964
- this.minWidthFrom = update.changes.mapPos(this.minWidthFrom, 1);
1965
- this.minWidthTo = update.changes.mapPos(this.minWidthTo, 1);
2015
+ /**
2016
+ The direction of this span.
2017
+ */
2018
+ get dir() { return this.level % 2 ? RTL : LTR; }
2019
+ /**
2020
+ @internal
2021
+ */
2022
+ side(end, dir) { return (this.dir == dir) == end ? this.to : this.from; }
2023
+ /**
2024
+ @internal
2025
+ */
2026
+ static find(order, index, level, assoc) {
2027
+ let maybe = -1;
2028
+ for (let i = 0; i < order.length; i++) {
2029
+ let span = order[i];
2030
+ if (span.from <= index && span.to >= index) {
2031
+ if (span.level == level)
2032
+ return i;
2033
+ // When multiple spans match, if assoc != 0, take the one that
2034
+ // covers that side, otherwise take the one with the minimum
2035
+ // level.
2036
+ if (maybe < 0 || (assoc != 0 ? (assoc < 0 ? span.from < index : span.to > index) : order[maybe].level > span.level))
2037
+ maybe = i;
1966
2038
  }
1967
2039
  }
1968
- if (this.view.inputState.composing < 0)
1969
- this.compositionDeco = Decoration.none;
1970
- else if (update.transactions.length)
1971
- this.compositionDeco = computeCompositionDeco(this.view, update.changes);
1972
- // When the DOM nodes around the selection are moved to another
1973
- // parent, Chrome sometimes reports a different selection through
1974
- // getSelection than the one that it actually shows to the user.
1975
- // This forces a selection update when lines are joined to work
1976
- // around that. Issue #54
1977
- if ((browser.ie || browser.chrome) && !this.compositionDeco.size && update &&
1978
- update.state.doc.lines != update.startState.doc.lines)
1979
- this.forceSelection = true;
1980
- let prevDeco = this.decorations, deco = this.updateDeco();
1981
- let decoDiff = findChangedDeco(prevDeco, deco, update.changes);
1982
- changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
1983
- if (this.dirty == 0 /* Not */ && changedRanges.length == 0) {
1984
- return false;
1985
- }
1986
- else {
1987
- this.updateInner(changedRanges, deco, update.startState.doc.length);
1988
- if (update.transactions.length)
1989
- this.lastUpdate = Date.now();
1990
- return true;
1991
- }
2040
+ if (maybe < 0)
2041
+ throw new RangeError("Index out of range");
2042
+ return maybe;
1992
2043
  }
1993
- reset(sel) {
1994
- if (this.dirty) {
1995
- this.view.observer.ignore(() => this.view.docView.sync());
1996
- this.dirty = 0 /* Not */;
1997
- this.updateSelection(true);
2044
+ }
2045
+ // Reused array of character types
2046
+ const types = [];
2047
+ function computeOrder(line, direction) {
2048
+ let len = line.length, outerType = direction == LTR ? 1 /* L */ : 2 /* R */, oppositeType = direction == LTR ? 2 /* R */ : 1 /* L */;
2049
+ if (!line || outerType == 1 /* L */ && !BidiRE.test(line))
2050
+ return trivialOrder(len);
2051
+ // W1. Examine each non-spacing mark (NSM) in the level run, and
2052
+ // change the type of the NSM to the type of the previous
2053
+ // character. If the NSM is at the start of the level run, it will
2054
+ // get the type of sor.
2055
+ // W2. Search backwards from each instance of a European number
2056
+ // until the first strong type (R, L, AL, or sor) is found. If an
2057
+ // AL is found, change the type of the European number to Arabic
2058
+ // number.
2059
+ // W3. Change all ALs to R.
2060
+ // (Left after this: L, R, EN, AN, ET, CS, NI)
2061
+ for (let i = 0, prev = outerType, prevStrong = outerType; i < len; i++) {
2062
+ let type = charType(line.charCodeAt(i));
2063
+ if (type == 512 /* NSM */)
2064
+ type = prev;
2065
+ else if (type == 8 /* EN */ && prevStrong == 4 /* AL */)
2066
+ type = 16 /* AN */;
2067
+ types[i] = type == 4 /* AL */ ? 2 /* R */ : type;
2068
+ if (type & 7 /* Strong */)
2069
+ prevStrong = type;
2070
+ prev = type;
2071
+ }
2072
+ // W5. A sequence of European terminators adjacent to European
2073
+ // numbers changes to all European numbers.
2074
+ // W6. Otherwise, separators and terminators change to Other
2075
+ // Neutral.
2076
+ // W7. Search backwards from each instance of a European number
2077
+ // until the first strong type (R, L, or sor) is found. If an L is
2078
+ // found, then change the type of the European number to L.
2079
+ // (Left after this: L, R, EN+AN, NI)
2080
+ for (let i = 0, prev = outerType, prevStrong = outerType; i < len; i++) {
2081
+ let type = types[i];
2082
+ if (type == 128 /* CS */) {
2083
+ if (i < len - 1 && prev == types[i + 1] && (prev & 24 /* Num */))
2084
+ type = types[i] = prev;
2085
+ else
2086
+ types[i] = 256 /* NI */;
1998
2087
  }
1999
- else {
2000
- this.updateSelection();
2088
+ else if (type == 64 /* ET */) {
2089
+ let end = i + 1;
2090
+ while (end < len && types[end] == 64 /* ET */)
2091
+ end++;
2092
+ let replace = (i && prev == 8 /* EN */) || (end < len && types[end] == 8 /* EN */) ? (prevStrong == 1 /* L */ ? 1 /* L */ : 8 /* EN */) : 256 /* NI */;
2093
+ for (let j = i; j < end; j++)
2094
+ types[j] = replace;
2095
+ i = end - 1;
2001
2096
  }
2002
- }
2003
- // Used by update and the constructor do perform the actual DOM
2004
- // update
2005
- updateInner(changes, deco, oldLength) {
2006
- this.view.viewState.mustMeasureContent = true;
2007
- this.updateChildren(changes, deco, oldLength);
2008
- let { observer } = this.view;
2009
- observer.ignore(() => {
2010
- // Lock the height during redrawing, since Chrome sometimes
2011
- // messes with the scroll position during DOM mutation (though
2012
- // no relayout is triggered and I cannot imagine how it can
2013
- // recompute the scroll position without a layout)
2014
- this.dom.style.height = this.view.viewState.contentHeight + "px";
2015
- this.dom.style.minWidth = this.minWidth ? this.minWidth + "px" : "";
2016
- // Chrome will sometimes, when DOM mutations occur directly
2017
- // around the selection, get confused and report a different
2018
- // selection from the one it displays (issue #218). This tries
2019
- // to detect that situation.
2020
- let track = browser.chrome || browser.ios ? { node: observer.selectionRange.focusNode, written: false } : undefined;
2021
- this.sync(track);
2022
- this.dirty = 0 /* Not */;
2023
- if (track && (track.written || observer.selectionRange.focusNode != track.node))
2024
- this.forceSelection = true;
2025
- this.dom.style.height = "";
2026
- });
2027
- let gaps = [];
2028
- if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
2029
- for (let child of this.children)
2030
- if (child instanceof BlockWidgetView && child.widget instanceof BlockGapWidget)
2031
- gaps.push(child.dom);
2032
- observer.updateGaps(gaps);
2033
- }
2034
- updateChildren(changes, deco, oldLength) {
2035
- let cursor = this.childCursor(oldLength);
2036
- for (let i = changes.length - 1;; i--) {
2037
- let next = i >= 0 ? changes[i] : null;
2038
- if (!next)
2039
- break;
2040
- let { fromA, toA, fromB, toB } = next;
2041
- let { content, breakAtStart, openStart, openEnd } = ContentBuilder.build(this.view.state.doc, fromB, toB, deco);
2042
- let { i: toI, off: toOff } = cursor.findPos(toA, 1);
2043
- let { i: fromI, off: fromOff } = cursor.findPos(fromA, -1);
2044
- this.replaceRange(fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd);
2097
+ else if (type == 8 /* EN */ && prevStrong == 1 /* L */) {
2098
+ types[i] = 1 /* L */;
2045
2099
  }
2100
+ prev = type;
2101
+ if (type & 7 /* Strong */)
2102
+ prevStrong = type;
2046
2103
  }
2047
- replaceRange(fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd) {
2048
- let before = this.children[fromI], last = content.length ? content[content.length - 1] : null;
2049
- let breakAtEnd = last ? last.breakAfter : breakAtStart;
2050
- // Change within a single line
2051
- if (fromI == toI && !breakAtStart && !breakAtEnd && content.length < 2 &&
2052
- before.merge(fromOff, toOff, content.length ? last : null, fromOff == 0, openStart, openEnd))
2053
- return;
2054
- let after = this.children[toI];
2055
- // Make sure the end of the line after the update is preserved in `after`
2056
- if (toOff < after.length) {
2057
- // If we're splitting a line, separate part of the start line to
2058
- // avoid that being mangled when updating the start line.
2059
- if (fromI == toI) {
2060
- after = after.split(toOff);
2061
- toOff = 0;
2104
+ // N0. Process bracket pairs in an isolating run sequence
2105
+ // sequentially in the logical order of the text positions of the
2106
+ // opening paired brackets using the logic given below. Within this
2107
+ // scope, bidirectional types EN and AN are treated as R.
2108
+ for (let i = 0, sI = 0, context = 0, ch, br, type; i < len; i++) {
2109
+ // Keeps [startIndex, type, strongSeen] triples for each open
2110
+ // bracket on BracketStack.
2111
+ if (br = Brackets[ch = line.charCodeAt(i)]) {
2112
+ if (br < 0) { // Closing bracket
2113
+ for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
2114
+ if (BracketStack[sJ + 1] == -br) {
2115
+ let flags = BracketStack[sJ + 2];
2116
+ let type = (flags & 2 /* EmbedInside */) ? outerType :
2117
+ !(flags & 4 /* OppositeInside */) ? 0 :
2118
+ (flags & 1 /* OppositeBefore */) ? oppositeType : outerType;
2119
+ if (type)
2120
+ types[i] = types[BracketStack[sJ]] = type;
2121
+ sI = sJ;
2122
+ break;
2123
+ }
2124
+ }
2062
2125
  }
2063
- // If the element after the replacement should be merged with
2064
- // the last replacing element, update `content`
2065
- if (!breakAtEnd && last && after.merge(0, toOff, last, true, 0, openEnd)) {
2066
- content[content.length - 1] = after;
2126
+ else if (BracketStack.length == 189 /* MaxDepth */) {
2127
+ break;
2067
2128
  }
2068
2129
  else {
2069
- // Remove the start of the after element, if necessary, and
2070
- // add it to `content`.
2071
- if (toOff)
2072
- after.merge(0, toOff, null, false, 0, openEnd);
2073
- content.push(after);
2130
+ BracketStack[sI++] = i;
2131
+ BracketStack[sI++] = ch;
2132
+ BracketStack[sI++] = context;
2074
2133
  }
2075
2134
  }
2076
- else if (after.breakAfter) {
2077
- // The element at `toI` is entirely covered by this range.
2078
- // Preserve its line break, if any.
2079
- if (last)
2080
- last.breakAfter = 1;
2081
- else
2082
- breakAtStart = 1;
2135
+ else if ((type = types[i]) == 2 /* R */ || type == 1 /* L */) {
2136
+ let embed = type == outerType;
2137
+ context = embed ? 0 : 1 /* OppositeBefore */;
2138
+ for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
2139
+ let cur = BracketStack[sJ + 2];
2140
+ if (cur & 2 /* EmbedInside */)
2141
+ break;
2142
+ if (embed) {
2143
+ BracketStack[sJ + 2] |= 2 /* EmbedInside */;
2144
+ }
2145
+ else {
2146
+ if (cur & 4 /* OppositeInside */)
2147
+ break;
2148
+ BracketStack[sJ + 2] |= 4 /* OppositeInside */;
2149
+ }
2150
+ }
2083
2151
  }
2084
- // Since we've handled the next element from the current elements
2085
- // now, make sure `toI` points after that.
2086
- toI++;
2087
- before.breakAfter = breakAtStart;
2088
- if (fromOff > 0) {
2089
- if (!breakAtStart && content.length && before.merge(fromOff, before.length, content[0], false, openStart, 0)) {
2090
- before.breakAfter = content.shift().breakAfter;
2152
+ }
2153
+ // N1. A sequence of neutrals takes the direction of the
2154
+ // surrounding strong text if the text on both sides has the same
2155
+ // direction. European and Arabic numbers act as if they were R in
2156
+ // terms of their influence on neutrals. Start-of-level-run (sor)
2157
+ // and end-of-level-run (eor) are used at level run boundaries.
2158
+ // N2. Any remaining neutrals take the embedding direction.
2159
+ // (Left after this: L, R, EN+AN)
2160
+ for (let i = 0; i < len; i++) {
2161
+ if (types[i] == 256 /* NI */) {
2162
+ let end = i + 1;
2163
+ while (end < len && types[end] == 256 /* NI */)
2164
+ end++;
2165
+ let beforeL = (i ? types[i - 1] : outerType) == 1 /* L */;
2166
+ let afterL = (end < len ? types[end] : outerType) == 1 /* L */;
2167
+ let replace = beforeL == afterL ? (beforeL ? 1 /* L */ : 2 /* R */) : outerType;
2168
+ for (let j = i; j < end; j++)
2169
+ types[j] = replace;
2170
+ i = end - 1;
2171
+ }
2172
+ }
2173
+ // Here we depart from the documented algorithm, in order to avoid
2174
+ // building up an actual levels array. Since there are only three
2175
+ // levels (0, 1, 2) in an implementation that doesn't take
2176
+ // explicit embedding into account, we can build up the order on
2177
+ // the fly, without following the level-based algorithm.
2178
+ let order = [];
2179
+ if (outerType == 1 /* L */) {
2180
+ for (let i = 0; i < len;) {
2181
+ let start = i, rtl = types[i++] != 1 /* L */;
2182
+ while (i < len && rtl == (types[i] != 1 /* L */))
2183
+ i++;
2184
+ if (rtl) {
2185
+ for (let j = i; j > start;) {
2186
+ let end = j, l = types[--j] != 2 /* R */;
2187
+ while (j > start && l == (types[j - 1] != 2 /* R */))
2188
+ j--;
2189
+ order.push(new BidiSpan(j, end, l ? 2 : 1));
2190
+ }
2091
2191
  }
2092
- else if (fromOff < before.length || before.children.length && before.children[before.children.length - 1].length == 0) {
2093
- before.merge(fromOff, before.length, null, false, openStart, 0);
2192
+ else {
2193
+ order.push(new BidiSpan(start, i, 0));
2094
2194
  }
2095
- fromI++;
2096
2195
  }
2097
- // Try to merge widgets on the boundaries of the replacement
2098
- while (fromI < toI && content.length) {
2099
- if (this.children[toI - 1].match(content[content.length - 1]))
2100
- toI--, content.pop();
2101
- else if (this.children[fromI].match(content[0]))
2102
- fromI++, content.shift();
2103
- else
2104
- break;
2196
+ }
2197
+ else {
2198
+ for (let i = 0; i < len;) {
2199
+ let start = i, rtl = types[i++] == 2 /* R */;
2200
+ while (i < len && rtl == (types[i] == 2 /* R */))
2201
+ i++;
2202
+ order.push(new BidiSpan(start, i, rtl ? 1 : 2));
2105
2203
  }
2106
- if (fromI < toI || content.length)
2107
- this.replaceChildren(fromI, toI, content);
2108
2204
  }
2109
- // Sync the DOM selection to this.state.selection
2110
- updateSelection(mustRead = false, fromPointer = false) {
2111
- if (mustRead)
2112
- this.view.observer.readSelectionRange();
2113
- if (!(fromPointer || this.mayControlSelection()) ||
2114
- browser.ios && this.view.inputState.rapidCompositionStart)
2115
- return;
2116
- let force = this.forceSelection;
2117
- this.forceSelection = false;
2118
- let main = this.view.state.selection.main;
2119
- // FIXME need to handle the case where the selection falls inside a block range
2120
- let anchor = this.domAtPos(main.anchor);
2121
- let head = main.empty ? anchor : this.domAtPos(main.head);
2122
- // Always reset on Firefox when next to an uneditable node to
2123
- // avoid invisible cursor bugs (#111)
2124
- if (browser.gecko && main.empty && betweenUneditable(anchor)) {
2125
- let dummy = document.createTextNode("");
2126
- this.view.observer.ignore(() => anchor.node.insertBefore(dummy, anchor.node.childNodes[anchor.offset] || null));
2127
- anchor = head = new DOMPos(dummy, 0);
2128
- force = true;
2129
- }
2130
- let domSel = this.view.observer.selectionRange;
2131
- // If the selection is already here, or in an equivalent position, don't touch it
2132
- if (force || !domSel.focusNode ||
2133
- !isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
2134
- !isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
2135
- this.view.observer.ignore(() => {
2136
- // Chrome Android will hide the virtual keyboard when tapping
2137
- // inside an uneditable node, and not bring it back when we
2138
- // move the cursor to its proper position. This tries to
2139
- // restore the keyboard by cycling focus.
2140
- if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, this.dom)) {
2141
- this.dom.blur();
2142
- this.dom.focus({ preventScroll: true });
2143
- }
2144
- let rawSel = getSelection(this.root);
2145
- if (main.empty) {
2146
- // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
2147
- if (browser.gecko) {
2148
- let nextTo = nextToUneditable(anchor.node, anchor.offset);
2149
- if (nextTo && nextTo != (1 /* Before */ | 2 /* After */)) {
2150
- let text = nearbyTextNode(anchor.node, anchor.offset, nextTo == 1 /* Before */ ? 1 : -1);
2151
- if (text)
2152
- anchor = new DOMPos(text, nextTo == 1 /* Before */ ? 0 : text.nodeValue.length);
2153
- }
2154
- }
2155
- rawSel.collapse(anchor.node, anchor.offset);
2156
- if (main.bidiLevel != null && domSel.cursorBidiLevel != null)
2157
- domSel.cursorBidiLevel = main.bidiLevel;
2158
- }
2159
- else if (rawSel.extend) {
2160
- // Selection.extend can be used to create an 'inverted' selection
2161
- // (one where the focus is before the anchor), but not all
2162
- // browsers support it yet.
2163
- rawSel.collapse(anchor.node, anchor.offset);
2164
- rawSel.extend(head.node, head.offset);
2165
- }
2166
- else {
2167
- // Primitive (IE) way
2168
- let range = document.createRange();
2169
- if (main.anchor > main.head)
2170
- [anchor, head] = [head, anchor];
2171
- range.setEnd(head.node, head.offset);
2172
- range.setStart(anchor.node, anchor.offset);
2173
- rawSel.removeAllRanges();
2174
- rawSel.addRange(range);
2175
- }
2176
- });
2177
- this.view.observer.setSelectionRange(anchor, head);
2205
+ return order;
2206
+ }
2207
+ function trivialOrder(length) {
2208
+ return [new BidiSpan(0, length, 0)];
2209
+ }
2210
+ let movedOver = "";
2211
+ function moveVisually(line, order, dir, start, forward) {
2212
+ var _a;
2213
+ let startIndex = start.head - line.from, spanI = -1;
2214
+ if (startIndex == 0) {
2215
+ if (!forward || !line.length)
2216
+ return null;
2217
+ if (order[0].level != dir) {
2218
+ startIndex = order[0].side(false, dir);
2219
+ spanI = 0;
2178
2220
  }
2179
- this.impreciseAnchor = anchor.precise ? null : new DOMPos(domSel.anchorNode, domSel.anchorOffset);
2180
- this.impreciseHead = head.precise ? null : new DOMPos(domSel.focusNode, domSel.focusOffset);
2181
- }
2182
- enforceCursorAssoc() {
2183
- if (this.view.composing)
2184
- return;
2185
- let cursor = this.view.state.selection.main;
2186
- let sel = getSelection(this.root);
2187
- if (!cursor.empty || !cursor.assoc || !sel.modify)
2188
- return;
2189
- let line = LineView.find(this, cursor.head);
2190
- if (!line)
2191
- return;
2192
- let lineStart = line.posAtStart;
2193
- if (cursor.head == lineStart || cursor.head == lineStart + line.length)
2194
- return;
2195
- let before = this.coordsAt(cursor.head, -1), after = this.coordsAt(cursor.head, 1);
2196
- if (!before || !after || before.bottom > after.top)
2197
- return;
2198
- let dom = this.domAtPos(cursor.head + cursor.assoc);
2199
- sel.collapse(dom.node, dom.offset);
2200
- sel.modify("move", cursor.assoc < 0 ? "forward" : "backward", "lineboundary");
2201
- }
2202
- mayControlSelection() {
2203
- return this.view.state.facet(editable) ? this.root.activeElement == this.dom
2204
- : hasSelection(this.dom, this.view.observer.selectionRange);
2205
2221
  }
2206
- nearest(dom) {
2207
- for (let cur = dom; cur;) {
2208
- let domView = ContentView.get(cur);
2209
- if (domView && domView.rootView == this)
2210
- return domView;
2211
- cur = cur.parentNode;
2222
+ else if (startIndex == line.length) {
2223
+ if (forward)
2224
+ return null;
2225
+ let last = order[order.length - 1];
2226
+ if (last.level != dir) {
2227
+ startIndex = last.side(true, dir);
2228
+ spanI = order.length - 1;
2212
2229
  }
2213
- return null;
2214
- }
2215
- posFromDOM(node, offset) {
2216
- let view = this.nearest(node);
2217
- if (!view)
2218
- throw new RangeError("Trying to find position for a DOM position outside of the document");
2219
- return view.localPosFromDOM(node, offset) + view.posAtStart;
2220
2230
  }
2221
- domAtPos(pos) {
2222
- let { i, off } = this.childCursor().findPos(pos, -1);
2223
- for (; i < this.children.length - 1;) {
2224
- let child = this.children[i];
2225
- if (off < child.length || child instanceof LineView)
2226
- break;
2227
- i++;
2228
- off = 0;
2229
- }
2230
- return this.children[i].domAtPos(off);
2231
+ if (spanI < 0)
2232
+ spanI = BidiSpan.find(order, startIndex, (_a = start.bidiLevel) !== null && _a !== void 0 ? _a : -1, start.assoc);
2233
+ let span = order[spanI];
2234
+ // End of span. (But not end of line--that was checked for above.)
2235
+ if (startIndex == span.side(forward, dir)) {
2236
+ span = order[spanI += forward ? 1 : -1];
2237
+ startIndex = span.side(!forward, dir);
2231
2238
  }
2232
- coordsAt(pos, side) {
2233
- for (let off = this.length, i = this.children.length - 1;; i--) {
2234
- let child = this.children[i], start = off - child.breakAfter - child.length;
2235
- if (pos > start ||
2236
- (pos == start && child.type != BlockType.WidgetBefore && child.type != BlockType.WidgetAfter &&
2237
- (!i || side == 2 || this.children[i - 1].breakAfter ||
2238
- (this.children[i - 1].type == BlockType.WidgetBefore && side > -2))))
2239
- return child.coordsAt(pos - start, side);
2240
- off = start;
2241
- }
2239
+ let indexForward = forward == (span.dir == dir);
2240
+ let nextIndex = findClusterBreak(line.text, startIndex, indexForward);
2241
+ movedOver = line.text.slice(Math.min(startIndex, nextIndex), Math.max(startIndex, nextIndex));
2242
+ if (nextIndex != span.side(forward, dir))
2243
+ return EditorSelection.cursor(nextIndex + line.from, indexForward ? -1 : 1, span.level);
2244
+ let nextSpan = spanI == (forward ? order.length - 1 : 0) ? null : order[spanI + (forward ? 1 : -1)];
2245
+ if (!nextSpan && span.level != dir)
2246
+ return EditorSelection.cursor(forward ? line.to : line.from, forward ? -1 : 1, dir);
2247
+ if (nextSpan && nextSpan.level < span.level)
2248
+ return EditorSelection.cursor(nextSpan.side(!forward, dir) + line.from, forward ? 1 : -1, nextSpan.level);
2249
+ return EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
2250
+ }
2251
+
2252
+ class DOMReader {
2253
+ constructor(points, view) {
2254
+ this.points = points;
2255
+ this.view = view;
2256
+ this.text = "";
2257
+ this.lineBreak = view.state.lineBreak;
2242
2258
  }
2243
- measureVisibleLineHeights() {
2244
- let result = [], { from, to } = this.view.viewState.viewport;
2245
- let minWidth = Math.max(this.view.scrollDOM.clientWidth, this.minWidth) + 1;
2246
- for (let pos = 0, i = 0; i < this.children.length; i++) {
2247
- let child = this.children[i], end = pos + child.length;
2248
- if (end > to)
2259
+ readRange(start, end) {
2260
+ if (!start)
2261
+ return this;
2262
+ let parent = start.parentNode;
2263
+ for (let cur = start;;) {
2264
+ this.findPointBefore(parent, cur);
2265
+ this.readNode(cur);
2266
+ let next = cur.nextSibling;
2267
+ if (next == end)
2249
2268
  break;
2250
- if (pos >= from) {
2251
- result.push(child.dom.getBoundingClientRect().height);
2252
- let width = child.dom.scrollWidth;
2253
- if (width > minWidth) {
2254
- this.minWidth = minWidth = width;
2255
- this.minWidthFrom = pos;
2256
- this.minWidthTo = end;
2257
- }
2258
- }
2259
- pos = end + child.breakAfter;
2269
+ let view = ContentView.get(cur), nextView = ContentView.get(next);
2270
+ if (view && nextView ? view.breakAfter :
2271
+ (view ? view.breakAfter : isBlockElement(cur)) ||
2272
+ (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
2273
+ this.text += this.lineBreak;
2274
+ cur = next;
2260
2275
  }
2261
- return result;
2276
+ this.findPointBefore(parent, end);
2277
+ return this;
2262
2278
  }
2263
- measureTextSize() {
2264
- for (let child of this.children) {
2265
- if (child instanceof LineView) {
2266
- let measure = child.measureTextSize();
2267
- if (measure)
2268
- return measure;
2269
- }
2279
+ readNode(node) {
2280
+ if (node.cmIgnore)
2281
+ return;
2282
+ let view = ContentView.get(node);
2283
+ let fromView = view && view.overrideDOMText;
2284
+ let text;
2285
+ if (fromView != null)
2286
+ text = fromView.sliceString(0, undefined, this.lineBreak);
2287
+ else if (node.nodeType == 3)
2288
+ text = node.nodeValue;
2289
+ else if (node.nodeName == "BR")
2290
+ text = node.nextSibling ? this.lineBreak : "";
2291
+ else if (node.nodeType == 1)
2292
+ this.readRange(node.firstChild, null);
2293
+ if (text != null) {
2294
+ this.findPointIn(node, text.length);
2295
+ this.text += text;
2296
+ // Chrome inserts two newlines when pressing shift-enter at the
2297
+ // end of a line. This drops one of those.
2298
+ if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
2299
+ this.text = this.text.slice(0, -1);
2270
2300
  }
2271
- // If no workable line exists, force a layout of a measurable element
2272
- let dummy = document.createElement("div"), lineHeight, charWidth;
2273
- dummy.className = "cm-line";
2274
- dummy.textContent = "abc def ghi jkl mno pqr stu";
2275
- this.view.observer.ignore(() => {
2276
- this.dom.appendChild(dummy);
2277
- let rect = clientRectsFor(dummy.firstChild)[0];
2278
- lineHeight = dummy.getBoundingClientRect().height;
2279
- charWidth = rect ? rect.width / 27 : 7;
2280
- dummy.remove();
2281
- });
2282
- return { lineHeight, charWidth };
2283
2301
  }
2284
- childCursor(pos = this.length) {
2285
- // Move back to start of last element when possible, so that
2286
- // `ChildCursor.findPos` doesn't have to deal with the edge case
2287
- // of being after the last element.
2288
- let i = this.children.length;
2289
- if (i)
2290
- pos -= this.children[--i].length;
2291
- return new ChildCursor(this.children, pos, i);
2302
+ findPointBefore(node, next) {
2303
+ for (let point of this.points)
2304
+ if (point.node == node && node.childNodes[point.offset] == next)
2305
+ point.pos = this.text.length;
2292
2306
  }
2293
- computeBlockGapDeco() {
2294
- let deco = [], vs = this.view.viewState;
2295
- for (let pos = 0, i = 0;; i++) {
2296
- let next = i == vs.viewports.length ? null : vs.viewports[i];
2297
- let end = next ? next.from - 1 : this.length;
2298
- if (end > pos) {
2299
- let height = vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top;
2300
- deco.push(Decoration.replace({ widget: new BlockGapWidget(height), block: true, inclusive: true }).range(pos, end));
2301
- }
2302
- if (!next)
2303
- break;
2304
- pos = next.to + 1;
2305
- }
2306
- return Decoration.set(deco);
2307
- }
2308
- updateDeco() {
2309
- return this.decorations = [
2310
- ...this.view.pluginField(PluginField.decorations),
2311
- ...this.view.state.facet(decorations),
2312
- this.compositionDeco,
2313
- this.computeBlockGapDeco(),
2314
- this.view.viewState.lineGapDeco
2315
- ];
2316
- }
2317
- scrollIntoView({ range, center }) {
2318
- let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
2319
- if (!rect)
2320
- return;
2321
- if (!range.empty && (other = this.coordsAt(range.anchor, range.anchor > range.head ? -1 : 1)))
2322
- rect = { left: Math.min(rect.left, other.left), top: Math.min(rect.top, other.top),
2323
- right: Math.max(rect.right, other.right), bottom: Math.max(rect.bottom, other.bottom) };
2324
- let mLeft = 0, mRight = 0, mTop = 0, mBottom = 0;
2325
- for (let margins of this.view.pluginField(PluginField.scrollMargins))
2326
- if (margins) {
2327
- let { left, right, top, bottom } = margins;
2328
- if (left != null)
2329
- mLeft = Math.max(mLeft, left);
2330
- if (right != null)
2331
- mRight = Math.max(mRight, right);
2332
- if (top != null)
2333
- mTop = Math.max(mTop, top);
2334
- if (bottom != null)
2335
- mBottom = Math.max(mBottom, bottom);
2336
- }
2337
- scrollRectIntoView(this.view.scrollDOM, {
2338
- left: rect.left - mLeft, top: rect.top - mTop,
2339
- right: rect.right + mRight, bottom: rect.bottom + mBottom
2340
- }, range.head < range.anchor ? -1 : 1, center);
2307
+ findPointIn(node, maxLen) {
2308
+ for (let point of this.points)
2309
+ if (point.node == node)
2310
+ point.pos = this.text.length + Math.min(point.offset, maxLen);
2341
2311
  }
2342
2312
  }
2343
- function betweenUneditable(pos) {
2344
- return pos.node.nodeType == 1 && pos.node.firstChild &&
2345
- (pos.offset == 0 || pos.node.childNodes[pos.offset - 1].contentEditable == "false") &&
2346
- (pos.offset == pos.node.childNodes.length || pos.node.childNodes[pos.offset].contentEditable == "false");
2347
- }
2348
- class BlockGapWidget extends WidgetType {
2349
- constructor(height) {
2350
- super();
2351
- this.height = height;
2352
- }
2353
- toDOM() {
2354
- let elt = document.createElement("div");
2355
- this.updateDOM(elt);
2356
- return elt;
2357
- }
2358
- eq(other) { return other.height == this.height; }
2359
- updateDOM(elt) {
2360
- elt.style.height = this.height + "px";
2361
- return true;
2362
- }
2363
- get estimatedHeight() { return this.height; }
2313
+ function isBlockElement(node) {
2314
+ return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
2364
2315
  }
2365
- function computeCompositionDeco(view, changes) {
2366
- let sel = view.observer.selectionRange;
2367
- let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
2368
- if (!textNode)
2369
- return Decoration.none;
2370
- let cView = view.docView.nearest(textNode);
2371
- let from, to, topNode = textNode;
2372
- if (cView instanceof InlineView) {
2373
- while (cView.parent instanceof InlineView)
2374
- cView = cView.parent;
2375
- from = cView.posAtStart;
2376
- to = from + cView.length;
2377
- topNode = cView.dom;
2378
- }
2379
- else if (cView instanceof LineView) {
2380
- while (topNode.parentNode != cView.dom)
2381
- topNode = topNode.parentNode;
2382
- let prev = topNode.previousSibling;
2383
- while (prev && !ContentView.get(prev))
2384
- prev = prev.previousSibling;
2385
- from = to = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2386
- }
2387
- else {
2388
- return Decoration.none;
2389
- }
2390
- let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2391
- let text = textNode.nodeValue, { state } = view;
2392
- if (newTo - newFrom < text.length) {
2393
- if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2394
- newTo = newFrom + text.length;
2395
- else if (state.sliceDoc(Math.max(0, newTo - text.length), newTo) == text)
2396
- newFrom = newTo - text.length;
2397
- else
2398
- return Decoration.none;
2399
- }
2400
- else if (state.sliceDoc(newFrom, newTo) != text) {
2401
- return Decoration.none;
2316
+ class DOMPoint {
2317
+ constructor(node, offset) {
2318
+ this.node = node;
2319
+ this.offset = offset;
2320
+ this.pos = -1;
2402
2321
  }
2403
- return Decoration.set(Decoration.replace({ widget: new CompositionWidget(topNode, textNode) }).range(newFrom, newTo));
2404
2322
  }
2405
- class CompositionWidget extends WidgetType {
2406
- constructor(top, text) {
2323
+
2324
+ class DocView extends ContentView {
2325
+ constructor(view) {
2407
2326
  super();
2408
- this.top = top;
2409
- this.text = text;
2327
+ this.view = view;
2328
+ this.compositionDeco = Decoration.none;
2329
+ this.decorations = [];
2330
+ // Track a minimum width for the editor. When measuring sizes in
2331
+ // measureVisibleLineHeights, this is updated to point at the width
2332
+ // of a given element and its extent in the document. When a change
2333
+ // happens in that range, these are reset. That way, once we've seen
2334
+ // a line/element of a given length, we keep the editor wide enough
2335
+ // to fit at least that element, until it is changed, at which point
2336
+ // we forget it again.
2337
+ this.minWidth = 0;
2338
+ this.minWidthFrom = 0;
2339
+ this.minWidthTo = 0;
2340
+ // Track whether the DOM selection was set in a lossy way, so that
2341
+ // we don't mess it up when reading it back it
2342
+ this.impreciseAnchor = null;
2343
+ this.impreciseHead = null;
2344
+ this.forceSelection = false;
2345
+ // Used by the resize observer to ignore resizes that we caused
2346
+ // ourselves
2347
+ this.lastUpdate = Date.now();
2348
+ this.setDOM(view.contentDOM);
2349
+ this.children = [new LineView];
2350
+ this.children[0].setParent(this);
2351
+ this.updateInner([new ChangedRange(0, 0, 0, view.state.doc.length)], this.updateDeco(), 0);
2410
2352
  }
2411
- eq(other) { return this.top == other.top && this.text == other.text; }
2412
- toDOM() { return this.top; }
2413
- ignoreEvent() { return false; }
2414
- get customView() { return CompositionView; }
2415
- }
2416
- function nearbyTextNode(node, offset, side) {
2417
- for (;;) {
2418
- if (node.nodeType == 3)
2419
- return node;
2420
- if (node.nodeType == 1 && offset > 0 && side <= 0) {
2421
- node = node.childNodes[offset - 1];
2422
- offset = maxOffset(node);
2353
+ get root() { return this.view.root; }
2354
+ get editorView() { return this.view; }
2355
+ get length() { return this.view.state.doc.length; }
2356
+ // Update the document view to a given state. scrollIntoView can be
2357
+ // used as a hint to compute a new viewport that includes that
2358
+ // position, if we know the editor is going to scroll that position
2359
+ // into view.
2360
+ update(update) {
2361
+ let changedRanges = update.changedRanges;
2362
+ if (this.minWidth > 0 && changedRanges.length) {
2363
+ if (!changedRanges.every(({ fromA, toA }) => toA < this.minWidthFrom || fromA > this.minWidthTo)) {
2364
+ this.minWidth = this.minWidthFrom = this.minWidthTo = 0;
2365
+ }
2366
+ else {
2367
+ this.minWidthFrom = update.changes.mapPos(this.minWidthFrom, 1);
2368
+ this.minWidthTo = update.changes.mapPos(this.minWidthTo, 1);
2369
+ }
2423
2370
  }
2424
- else if (node.nodeType == 1 && offset < node.childNodes.length && side >= 0) {
2425
- node = node.childNodes[offset];
2426
- offset = 0;
2371
+ if (this.view.inputState.composing < 0)
2372
+ this.compositionDeco = Decoration.none;
2373
+ else if (update.transactions.length || this.dirty)
2374
+ this.compositionDeco = computeCompositionDeco(this.view, update.changes);
2375
+ // When the DOM nodes around the selection are moved to another
2376
+ // parent, Chrome sometimes reports a different selection through
2377
+ // getSelection than the one that it actually shows to the user.
2378
+ // This forces a selection update when lines are joined to work
2379
+ // around that. Issue #54
2380
+ if ((browser.ie || browser.chrome) && !this.compositionDeco.size && update &&
2381
+ update.state.doc.lines != update.startState.doc.lines)
2382
+ this.forceSelection = true;
2383
+ let prevDeco = this.decorations, deco = this.updateDeco();
2384
+ let decoDiff = findChangedDeco(prevDeco, deco, update.changes);
2385
+ changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
2386
+ if (this.dirty == 0 /* Not */ && changedRanges.length == 0) {
2387
+ return false;
2427
2388
  }
2428
2389
  else {
2429
- return null;
2390
+ this.updateInner(changedRanges, deco, update.startState.doc.length);
2391
+ if (update.transactions.length)
2392
+ this.lastUpdate = Date.now();
2393
+ return true;
2430
2394
  }
2431
2395
  }
2432
- }
2433
- function nextToUneditable(node, offset) {
2434
- if (node.nodeType != 1)
2435
- return 0;
2436
- return (offset && node.childNodes[offset - 1].contentEditable == "false" ? 1 /* Before */ : 0) |
2437
- (offset < node.childNodes.length && node.childNodes[offset].contentEditable == "false" ? 2 /* After */ : 0);
2438
- }
2439
- class DecorationComparator$1 {
2440
- constructor() {
2441
- this.changes = [];
2442
- }
2443
- compareRange(from, to) { addRange(from, to, this.changes); }
2444
- comparePoint(from, to) { addRange(from, to, this.changes); }
2445
- }
2446
- function findChangedDeco(a, b, diff) {
2447
- let comp = new DecorationComparator$1;
2448
- RangeSet.compare(a, b, diff, comp);
2449
- return comp.changes;
2450
- }
2451
- function inUneditable(node, inside) {
2452
- for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
2453
- if (cur.nodeType == 1 && cur.contentEditable == 'false') {
2454
- return true;
2396
+ reset(sel) {
2397
+ if (this.dirty) {
2398
+ this.view.observer.ignore(() => this.view.docView.sync());
2399
+ this.dirty = 0 /* Not */;
2400
+ this.updateSelection(true);
2401
+ }
2402
+ else {
2403
+ this.updateSelection();
2455
2404
  }
2456
2405
  }
2457
- return false;
2458
- }
2459
-
2460
- /**
2461
- Used to indicate [text direction](https://codemirror.net/6/docs/ref/#view.EditorView.textDirection).
2462
- */
2463
- var Direction = /*@__PURE__*/(function (Direction) {
2464
- // (These are chosen to match the base levels, in bidi algorithm
2465
- // terms, of spans in that direction.)
2466
- /**
2467
- Left-to-right.
2468
- */
2469
- Direction[Direction["LTR"] = 0] = "LTR";
2470
- /**
2471
- Right-to-left.
2472
- */
2473
- Direction[Direction["RTL"] = 1] = "RTL";
2474
- return Direction})(Direction || (Direction = {}));
2475
- const LTR = Direction.LTR, RTL = Direction.RTL;
2476
- // Decode a string with each type encoded as log2(type)
2477
- function dec(str) {
2478
- let result = [];
2479
- for (let i = 0; i < str.length; i++)
2480
- result.push(1 << +str[i]);
2481
- return result;
2482
- }
2483
- // Character types for codepoints 0 to 0xf8
2484
- const LowTypes = /*@__PURE__*/dec("88888888888888888888888888888888888666888888787833333333337888888000000000000000000000000008888880000000000000000000000000088888888888888888888888888888888888887866668888088888663380888308888800000000000000000000000800000000000000000000000000000008");
2485
- // Character types for codepoints 0x600 to 0x6f9
2486
- const ArabicTypes = /*@__PURE__*/dec("4444448826627288999999999992222222222222222222222222222222222222222222222229999999999999999999994444444444644222822222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222999999949999999229989999223333333333");
2487
- const Brackets = /*@__PURE__*/Object.create(null), BracketStack = [];
2488
- // There's a lot more in
2489
- // https://www.unicode.org/Public/UCD/latest/ucd/BidiBrackets.txt,
2490
- // which are left out to keep code size down.
2491
- for (let p of ["()", "[]", "{}"]) {
2492
- let l = /*@__PURE__*/p.charCodeAt(0), r = /*@__PURE__*/p.charCodeAt(1);
2493
- Brackets[l] = r;
2494
- Brackets[r] = -l;
2495
- }
2496
- function charType(ch) {
2497
- return ch <= 0xf7 ? LowTypes[ch] :
2498
- 0x590 <= ch && ch <= 0x5f4 ? 2 /* R */ :
2499
- 0x600 <= ch && ch <= 0x6f9 ? ArabicTypes[ch - 0x600] :
2500
- 0x6ee <= ch && ch <= 0x8ac ? 4 /* AL */ :
2501
- 0x2000 <= ch && ch <= 0x200b ? 256 /* NI */ :
2502
- ch == 0x200c ? 256 /* NI */ : 1 /* L */;
2503
- }
2504
- const BidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
2505
- /**
2506
- Represents a contiguous range of text that has a single direction
2507
- (as in left-to-right or right-to-left).
2508
- */
2509
- class BidiSpan {
2510
- /**
2511
- @internal
2512
- */
2513
- constructor(
2514
- /**
2515
- The start of the span (relative to the start of the line).
2516
- */
2517
- from,
2518
- /**
2519
- The end of the span.
2520
- */
2521
- to,
2522
- /**
2523
- The ["bidi
2524
- level"](https://unicode.org/reports/tr9/#Basic_Display_Algorithm)
2525
- of the span (in this context, 0 means
2526
- left-to-right, 1 means right-to-left, 2 means left-to-right
2527
- number inside right-to-left text).
2528
- */
2529
- level) {
2530
- this.from = from;
2531
- this.to = to;
2532
- this.level = level;
2406
+ // Used by update and the constructor do perform the actual DOM
2407
+ // update
2408
+ updateInner(changes, deco, oldLength) {
2409
+ this.view.viewState.mustMeasureContent = true;
2410
+ this.updateChildren(changes, deco, oldLength);
2411
+ let { observer } = this.view;
2412
+ observer.ignore(() => {
2413
+ // Lock the height during redrawing, since Chrome sometimes
2414
+ // messes with the scroll position during DOM mutation (though
2415
+ // no relayout is triggered and I cannot imagine how it can
2416
+ // recompute the scroll position without a layout)
2417
+ this.dom.style.height = this.view.viewState.contentHeight + "px";
2418
+ this.dom.style.minWidth = this.minWidth ? this.minWidth + "px" : "";
2419
+ // Chrome will sometimes, when DOM mutations occur directly
2420
+ // around the selection, get confused and report a different
2421
+ // selection from the one it displays (issue #218). This tries
2422
+ // to detect that situation.
2423
+ let track = browser.chrome || browser.ios ? { node: observer.selectionRange.focusNode, written: false } : undefined;
2424
+ this.sync(track);
2425
+ this.dirty = 0 /* Not */;
2426
+ if (track && (track.written || observer.selectionRange.focusNode != track.node))
2427
+ this.forceSelection = true;
2428
+ this.dom.style.height = "";
2429
+ });
2430
+ let gaps = [];
2431
+ if (this.view.viewport.from || this.view.viewport.to < this.view.state.doc.length)
2432
+ for (let child of this.children)
2433
+ if (child instanceof BlockWidgetView && child.widget instanceof BlockGapWidget)
2434
+ gaps.push(child.dom);
2435
+ observer.updateGaps(gaps);
2533
2436
  }
2534
- /**
2535
- The direction of this span.
2536
- */
2537
- get dir() { return this.level % 2 ? RTL : LTR; }
2538
- /**
2539
- @internal
2540
- */
2541
- side(end, dir) { return (this.dir == dir) == end ? this.to : this.from; }
2542
- /**
2543
- @internal
2544
- */
2545
- static find(order, index, level, assoc) {
2546
- let maybe = -1;
2547
- for (let i = 0; i < order.length; i++) {
2548
- let span = order[i];
2549
- if (span.from <= index && span.to >= index) {
2550
- if (span.level == level)
2551
- return i;
2552
- // When multiple spans match, if assoc != 0, take the one that
2553
- // covers that side, otherwise take the one with the minimum
2554
- // level.
2555
- if (maybe < 0 || (assoc != 0 ? (assoc < 0 ? span.from < index : span.to > index) : order[maybe].level > span.level))
2556
- maybe = i;
2557
- }
2437
+ updateChildren(changes, deco, oldLength) {
2438
+ let cursor = this.childCursor(oldLength);
2439
+ for (let i = changes.length - 1;; i--) {
2440
+ let next = i >= 0 ? changes[i] : null;
2441
+ if (!next)
2442
+ break;
2443
+ let { fromA, toA, fromB, toB } = next;
2444
+ let { content, breakAtStart, openStart, openEnd } = ContentBuilder.build(this.view.state.doc, fromB, toB, deco);
2445
+ let { i: toI, off: toOff } = cursor.findPos(toA, 1);
2446
+ let { i: fromI, off: fromOff } = cursor.findPos(fromA, -1);
2447
+ replaceRange(this, fromI, fromOff, toI, toOff, content, breakAtStart, openStart, openEnd);
2558
2448
  }
2559
- if (maybe < 0)
2560
- throw new RangeError("Index out of range");
2561
- return maybe;
2562
2449
  }
2563
- }
2564
- // Reused array of character types
2565
- const types = [];
2566
- function computeOrder(line, direction) {
2567
- let len = line.length, outerType = direction == LTR ? 1 /* L */ : 2 /* R */, oppositeType = direction == LTR ? 2 /* R */ : 1 /* L */;
2568
- if (!line || outerType == 1 /* L */ && !BidiRE.test(line))
2569
- return trivialOrder(len);
2570
- // W1. Examine each non-spacing mark (NSM) in the level run, and
2571
- // change the type of the NSM to the type of the previous
2572
- // character. If the NSM is at the start of the level run, it will
2573
- // get the type of sor.
2574
- // W2. Search backwards from each instance of a European number
2575
- // until the first strong type (R, L, AL, or sor) is found. If an
2576
- // AL is found, change the type of the European number to Arabic
2577
- // number.
2578
- // W3. Change all ALs to R.
2579
- // (Left after this: L, R, EN, AN, ET, CS, NI)
2580
- for (let i = 0, prev = outerType, prevStrong = outerType; i < len; i++) {
2581
- let type = charType(line.charCodeAt(i));
2582
- if (type == 512 /* NSM */)
2583
- type = prev;
2584
- else if (type == 8 /* EN */ && prevStrong == 4 /* AL */)
2585
- type = 16 /* AN */;
2586
- types[i] = type == 4 /* AL */ ? 2 /* R */ : type;
2587
- if (type & 7 /* Strong */)
2588
- prevStrong = type;
2589
- prev = type;
2450
+ // Sync the DOM selection to this.state.selection
2451
+ updateSelection(mustRead = false, fromPointer = false) {
2452
+ if (mustRead)
2453
+ this.view.observer.readSelectionRange();
2454
+ if (!(fromPointer || this.mayControlSelection()) ||
2455
+ browser.ios && this.view.inputState.rapidCompositionStart)
2456
+ return;
2457
+ let force = this.forceSelection;
2458
+ this.forceSelection = false;
2459
+ let main = this.view.state.selection.main;
2460
+ // FIXME need to handle the case where the selection falls inside a block range
2461
+ let anchor = this.domAtPos(main.anchor);
2462
+ let head = main.empty ? anchor : this.domAtPos(main.head);
2463
+ // Always reset on Firefox when next to an uneditable node to
2464
+ // avoid invisible cursor bugs (#111)
2465
+ if (browser.gecko && main.empty && betweenUneditable(anchor)) {
2466
+ let dummy = document.createTextNode("");
2467
+ this.view.observer.ignore(() => anchor.node.insertBefore(dummy, anchor.node.childNodes[anchor.offset] || null));
2468
+ anchor = head = new DOMPos(dummy, 0);
2469
+ force = true;
2470
+ }
2471
+ let domSel = this.view.observer.selectionRange;
2472
+ // If the selection is already here, or in an equivalent position, don't touch it
2473
+ if (force || !domSel.focusNode ||
2474
+ !isEquivalentPosition(anchor.node, anchor.offset, domSel.anchorNode, domSel.anchorOffset) ||
2475
+ !isEquivalentPosition(head.node, head.offset, domSel.focusNode, domSel.focusOffset)) {
2476
+ this.view.observer.ignore(() => {
2477
+ // Chrome Android will hide the virtual keyboard when tapping
2478
+ // inside an uneditable node, and not bring it back when we
2479
+ // move the cursor to its proper position. This tries to
2480
+ // restore the keyboard by cycling focus.
2481
+ if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, this.dom)) {
2482
+ this.dom.blur();
2483
+ this.dom.focus({ preventScroll: true });
2484
+ }
2485
+ let rawSel = getSelection(this.root);
2486
+ if (main.empty) {
2487
+ // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1612076
2488
+ if (browser.gecko) {
2489
+ let nextTo = nextToUneditable(anchor.node, anchor.offset);
2490
+ if (nextTo && nextTo != (1 /* Before */ | 2 /* After */)) {
2491
+ let text = nearbyTextNode(anchor.node, anchor.offset, nextTo == 1 /* Before */ ? 1 : -1);
2492
+ if (text)
2493
+ anchor = new DOMPos(text, nextTo == 1 /* Before */ ? 0 : text.nodeValue.length);
2494
+ }
2495
+ }
2496
+ rawSel.collapse(anchor.node, anchor.offset);
2497
+ if (main.bidiLevel != null && domSel.cursorBidiLevel != null)
2498
+ domSel.cursorBidiLevel = main.bidiLevel;
2499
+ }
2500
+ else if (rawSel.extend) {
2501
+ // Selection.extend can be used to create an 'inverted' selection
2502
+ // (one where the focus is before the anchor), but not all
2503
+ // browsers support it yet.
2504
+ rawSel.collapse(anchor.node, anchor.offset);
2505
+ rawSel.extend(head.node, head.offset);
2506
+ }
2507
+ else {
2508
+ // Primitive (IE) way
2509
+ let range = document.createRange();
2510
+ if (main.anchor > main.head)
2511
+ [anchor, head] = [head, anchor];
2512
+ range.setEnd(head.node, head.offset);
2513
+ range.setStart(anchor.node, anchor.offset);
2514
+ rawSel.removeAllRanges();
2515
+ rawSel.addRange(range);
2516
+ }
2517
+ });
2518
+ this.view.observer.setSelectionRange(anchor, head);
2519
+ }
2520
+ this.impreciseAnchor = anchor.precise ? null : new DOMPos(domSel.anchorNode, domSel.anchorOffset);
2521
+ this.impreciseHead = head.precise ? null : new DOMPos(domSel.focusNode, domSel.focusOffset);
2522
+ }
2523
+ enforceCursorAssoc() {
2524
+ if (this.compositionDeco.size)
2525
+ return;
2526
+ let cursor = this.view.state.selection.main;
2527
+ let sel = getSelection(this.root);
2528
+ if (!cursor.empty || !cursor.assoc || !sel.modify)
2529
+ return;
2530
+ let line = LineView.find(this, cursor.head);
2531
+ if (!line)
2532
+ return;
2533
+ let lineStart = line.posAtStart;
2534
+ if (cursor.head == lineStart || cursor.head == lineStart + line.length)
2535
+ return;
2536
+ let before = this.coordsAt(cursor.head, -1), after = this.coordsAt(cursor.head, 1);
2537
+ if (!before || !after || before.bottom > after.top)
2538
+ return;
2539
+ let dom = this.domAtPos(cursor.head + cursor.assoc);
2540
+ sel.collapse(dom.node, dom.offset);
2541
+ sel.modify("move", cursor.assoc < 0 ? "forward" : "backward", "lineboundary");
2590
2542
  }
2591
- // W5. A sequence of European terminators adjacent to European
2592
- // numbers changes to all European numbers.
2593
- // W6. Otherwise, separators and terminators change to Other
2594
- // Neutral.
2595
- // W7. Search backwards from each instance of a European number
2596
- // until the first strong type (R, L, or sor) is found. If an L is
2597
- // found, then change the type of the European number to L.
2598
- // (Left after this: L, R, EN+AN, NI)
2599
- for (let i = 0, prev = outerType, prevStrong = outerType; i < len; i++) {
2600
- let type = types[i];
2601
- if (type == 128 /* CS */) {
2602
- if (i < len - 1 && prev == types[i + 1] && (prev & 24 /* Num */))
2603
- type = types[i] = prev;
2604
- else
2605
- types[i] = 256 /* NI */;
2543
+ mayControlSelection() {
2544
+ return this.view.state.facet(editable) ? this.root.activeElement == this.dom
2545
+ : hasSelection(this.dom, this.view.observer.selectionRange);
2546
+ }
2547
+ nearest(dom) {
2548
+ for (let cur = dom; cur;) {
2549
+ let domView = ContentView.get(cur);
2550
+ if (domView && domView.rootView == this)
2551
+ return domView;
2552
+ cur = cur.parentNode;
2606
2553
  }
2607
- else if (type == 64 /* ET */) {
2608
- let end = i + 1;
2609
- while (end < len && types[end] == 64 /* ET */)
2610
- end++;
2611
- let replace = (i && prev == 8 /* EN */) || (end < len && types[end] == 8 /* EN */) ? (prevStrong == 1 /* L */ ? 1 /* L */ : 8 /* EN */) : 256 /* NI */;
2612
- for (let j = i; j < end; j++)
2613
- types[j] = replace;
2614
- i = end - 1;
2554
+ return null;
2555
+ }
2556
+ posFromDOM(node, offset) {
2557
+ let view = this.nearest(node);
2558
+ if (!view)
2559
+ throw new RangeError("Trying to find position for a DOM position outside of the document");
2560
+ return view.localPosFromDOM(node, offset) + view.posAtStart;
2561
+ }
2562
+ domAtPos(pos) {
2563
+ let { i, off } = this.childCursor().findPos(pos, -1);
2564
+ for (; i < this.children.length - 1;) {
2565
+ let child = this.children[i];
2566
+ if (off < child.length || child instanceof LineView)
2567
+ break;
2568
+ i++;
2569
+ off = 0;
2615
2570
  }
2616
- else if (type == 8 /* EN */ && prevStrong == 1 /* L */) {
2617
- types[i] = 1 /* L */;
2571
+ return this.children[i].domAtPos(off);
2572
+ }
2573
+ coordsAt(pos, side) {
2574
+ for (let off = this.length, i = this.children.length - 1;; i--) {
2575
+ let child = this.children[i], start = off - child.breakAfter - child.length;
2576
+ if (pos > start ||
2577
+ (pos == start && child.type != BlockType.WidgetBefore && child.type != BlockType.WidgetAfter &&
2578
+ (!i || side == 2 || this.children[i - 1].breakAfter ||
2579
+ (this.children[i - 1].type == BlockType.WidgetBefore && side > -2))))
2580
+ return child.coordsAt(pos - start, side);
2581
+ off = start;
2618
2582
  }
2619
- prev = type;
2620
- if (type & 7 /* Strong */)
2621
- prevStrong = type;
2622
2583
  }
2623
- // N0. Process bracket pairs in an isolating run sequence
2624
- // sequentially in the logical order of the text positions of the
2625
- // opening paired brackets using the logic given below. Within this
2626
- // scope, bidirectional types EN and AN are treated as R.
2627
- for (let i = 0, sI = 0, context = 0, ch, br, type; i < len; i++) {
2628
- // Keeps [startIndex, type, strongSeen] triples for each open
2629
- // bracket on BracketStack.
2630
- if (br = Brackets[ch = line.charCodeAt(i)]) {
2631
- if (br < 0) { // Closing bracket
2632
- for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
2633
- if (BracketStack[sJ + 1] == -br) {
2634
- let flags = BracketStack[sJ + 2];
2635
- let type = (flags & 2 /* EmbedInside */) ? outerType :
2636
- !(flags & 4 /* OppositeInside */) ? 0 :
2637
- (flags & 1 /* OppositeBefore */) ? oppositeType : outerType;
2638
- if (type)
2639
- types[i] = types[BracketStack[sJ]] = type;
2640
- sI = sJ;
2641
- break;
2584
+ measureVisibleLineHeights() {
2585
+ let result = [], { from, to } = this.view.viewState.viewport;
2586
+ let contentWidth = this.view.contentDOM.clientWidth;
2587
+ let isWider = contentWidth > Math.max(this.view.scrollDOM.clientWidth, this.minWidth) + 1;
2588
+ let widest = -1;
2589
+ for (let pos = 0, i = 0; i < this.children.length; i++) {
2590
+ let child = this.children[i], end = pos + child.length;
2591
+ if (end > to)
2592
+ break;
2593
+ if (pos >= from) {
2594
+ let childRect = child.dom.getBoundingClientRect();
2595
+ result.push(childRect.height);
2596
+ if (isWider) {
2597
+ let last = child.dom.lastChild;
2598
+ let rects = last ? clientRectsFor(last) : [];
2599
+ if (rects.length) {
2600
+ let rect = rects[rects.length - 1];
2601
+ let width = this.view.textDirection == Direction.LTR ? rect.right - childRect.left
2602
+ : childRect.right - rect.left;
2603
+ if (width > widest) {
2604
+ widest = width;
2605
+ this.minWidth = contentWidth;
2606
+ this.minWidthFrom = pos;
2607
+ this.minWidthTo = end;
2608
+ }
2642
2609
  }
2643
2610
  }
2644
2611
  }
2645
- else if (BracketStack.length == 189 /* MaxDepth */) {
2646
- break;
2647
- }
2648
- else {
2649
- BracketStack[sI++] = i;
2650
- BracketStack[sI++] = ch;
2651
- BracketStack[sI++] = context;
2652
- }
2612
+ pos = end + child.breakAfter;
2653
2613
  }
2654
- else if ((type = types[i]) == 2 /* R */ || type == 1 /* L */) {
2655
- let embed = type == outerType;
2656
- context = embed ? 0 : 1 /* OppositeBefore */;
2657
- for (let sJ = sI - 3; sJ >= 0; sJ -= 3) {
2658
- let cur = BracketStack[sJ + 2];
2659
- if (cur & 2 /* EmbedInside */)
2660
- break;
2661
- if (embed) {
2662
- BracketStack[sJ + 2] |= 2 /* EmbedInside */;
2663
- }
2664
- else {
2665
- if (cur & 4 /* OppositeInside */)
2666
- break;
2667
- BracketStack[sJ + 2] |= 4 /* OppositeInside */;
2668
- }
2614
+ return result;
2615
+ }
2616
+ measureTextSize() {
2617
+ for (let child of this.children) {
2618
+ if (child instanceof LineView) {
2619
+ let measure = child.measureTextSize();
2620
+ if (measure)
2621
+ return measure;
2669
2622
  }
2670
2623
  }
2624
+ // If no workable line exists, force a layout of a measurable element
2625
+ let dummy = document.createElement("div"), lineHeight, charWidth;
2626
+ dummy.className = "cm-line";
2627
+ dummy.textContent = "abc def ghi jkl mno pqr stu";
2628
+ this.view.observer.ignore(() => {
2629
+ this.dom.appendChild(dummy);
2630
+ let rect = clientRectsFor(dummy.firstChild)[0];
2631
+ lineHeight = dummy.getBoundingClientRect().height;
2632
+ charWidth = rect ? rect.width / 27 : 7;
2633
+ dummy.remove();
2634
+ });
2635
+ return { lineHeight, charWidth };
2671
2636
  }
2672
- // N1. A sequence of neutrals takes the direction of the
2673
- // surrounding strong text if the text on both sides has the same
2674
- // direction. European and Arabic numbers act as if they were R in
2675
- // terms of their influence on neutrals. Start-of-level-run (sor)
2676
- // and end-of-level-run (eor) are used at level run boundaries.
2677
- // N2. Any remaining neutrals take the embedding direction.
2678
- // (Left after this: L, R, EN+AN)
2679
- for (let i = 0; i < len; i++) {
2680
- if (types[i] == 256 /* NI */) {
2681
- let end = i + 1;
2682
- while (end < len && types[end] == 256 /* NI */)
2683
- end++;
2684
- let beforeL = (i ? types[i - 1] : outerType) == 1 /* L */;
2685
- let afterL = (end < len ? types[end] : outerType) == 1 /* L */;
2686
- let replace = beforeL == afterL ? (beforeL ? 1 /* L */ : 2 /* R */) : outerType;
2687
- for (let j = i; j < end; j++)
2688
- types[j] = replace;
2689
- i = end - 1;
2690
- }
2637
+ childCursor(pos = this.length) {
2638
+ // Move back to start of last element when possible, so that
2639
+ // `ChildCursor.findPos` doesn't have to deal with the edge case
2640
+ // of being after the last element.
2641
+ let i = this.children.length;
2642
+ if (i)
2643
+ pos -= this.children[--i].length;
2644
+ return new ChildCursor(this.children, pos, i);
2691
2645
  }
2692
- // Here we depart from the documented algorithm, in order to avoid
2693
- // building up an actual levels array. Since there are only three
2694
- // levels (0, 1, 2) in an implementation that doesn't take
2695
- // explicit embedding into account, we can build up the order on
2696
- // the fly, without following the level-based algorithm.
2697
- let order = [];
2698
- if (outerType == 1 /* L */) {
2699
- for (let i = 0; i < len;) {
2700
- let start = i, rtl = types[i++] != 1 /* L */;
2701
- while (i < len && rtl == (types[i] != 1 /* L */))
2702
- i++;
2703
- if (rtl) {
2704
- for (let j = i; j > start;) {
2705
- let end = j, l = types[--j] != 2 /* R */;
2706
- while (j > start && l == (types[j - 1] != 2 /* R */))
2707
- j--;
2708
- order.push(new BidiSpan(j, end, l ? 2 : 1));
2709
- }
2646
+ computeBlockGapDeco() {
2647
+ let deco = [], vs = this.view.viewState;
2648
+ for (let pos = 0, i = 0;; i++) {
2649
+ let next = i == vs.viewports.length ? null : vs.viewports[i];
2650
+ let end = next ? next.from - 1 : this.length;
2651
+ if (end > pos) {
2652
+ let height = vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top;
2653
+ deco.push(Decoration.replace({ widget: new BlockGapWidget(height), block: true, inclusive: true }).range(pos, end));
2710
2654
  }
2711
- else {
2712
- order.push(new BidiSpan(start, i, 0));
2655
+ if (!next)
2656
+ break;
2657
+ pos = next.to + 1;
2658
+ }
2659
+ return Decoration.set(deco);
2660
+ }
2661
+ updateDeco() {
2662
+ return this.decorations = [
2663
+ ...this.view.pluginField(PluginField.decorations),
2664
+ ...this.view.state.facet(decorations),
2665
+ this.compositionDeco,
2666
+ this.computeBlockGapDeco(),
2667
+ this.view.viewState.lineGapDeco
2668
+ ];
2669
+ }
2670
+ scrollIntoView({ range, center }) {
2671
+ let rect = this.coordsAt(range.head, range.empty ? range.assoc : range.head > range.anchor ? -1 : 1), other;
2672
+ if (!rect)
2673
+ return;
2674
+ if (!range.empty && (other = this.coordsAt(range.anchor, range.anchor > range.head ? -1 : 1)))
2675
+ rect = { left: Math.min(rect.left, other.left), top: Math.min(rect.top, other.top),
2676
+ right: Math.max(rect.right, other.right), bottom: Math.max(rect.bottom, other.bottom) };
2677
+ let mLeft = 0, mRight = 0, mTop = 0, mBottom = 0;
2678
+ for (let margins of this.view.pluginField(PluginField.scrollMargins))
2679
+ if (margins) {
2680
+ let { left, right, top, bottom } = margins;
2681
+ if (left != null)
2682
+ mLeft = Math.max(mLeft, left);
2683
+ if (right != null)
2684
+ mRight = Math.max(mRight, right);
2685
+ if (top != null)
2686
+ mTop = Math.max(mTop, top);
2687
+ if (bottom != null)
2688
+ mBottom = Math.max(mBottom, bottom);
2713
2689
  }
2714
- }
2690
+ scrollRectIntoView(this.view.scrollDOM, {
2691
+ left: rect.left - mLeft, top: rect.top - mTop,
2692
+ right: rect.right + mRight, bottom: rect.bottom + mBottom
2693
+ }, range.head < range.anchor ? -1 : 1, center);
2694
+ }
2695
+ }
2696
+ function betweenUneditable(pos) {
2697
+ return pos.node.nodeType == 1 && pos.node.firstChild &&
2698
+ (pos.offset == 0 || pos.node.childNodes[pos.offset - 1].contentEditable == "false") &&
2699
+ (pos.offset == pos.node.childNodes.length || pos.node.childNodes[pos.offset].contentEditable == "false");
2700
+ }
2701
+ class BlockGapWidget extends WidgetType {
2702
+ constructor(height) {
2703
+ super();
2704
+ this.height = height;
2705
+ }
2706
+ toDOM() {
2707
+ let elt = document.createElement("div");
2708
+ this.updateDOM(elt);
2709
+ return elt;
2710
+ }
2711
+ eq(other) { return other.height == this.height; }
2712
+ updateDOM(elt) {
2713
+ elt.style.height = this.height + "px";
2714
+ return true;
2715
+ }
2716
+ get estimatedHeight() { return this.height; }
2717
+ }
2718
+ function computeCompositionDeco(view, changes) {
2719
+ let sel = view.observer.selectionRange;
2720
+ let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
2721
+ if (!textNode)
2722
+ return Decoration.none;
2723
+ let cView = view.docView.nearest(textNode);
2724
+ if (!cView)
2725
+ return Decoration.none;
2726
+ let from, to, topNode = textNode;
2727
+ if (cView instanceof LineView) {
2728
+ while (topNode.parentNode != cView.dom)
2729
+ topNode = topNode.parentNode;
2730
+ let prev = topNode.previousSibling;
2731
+ while (prev && !ContentView.get(prev))
2732
+ prev = prev.previousSibling;
2733
+ from = to = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
2715
2734
  }
2716
2735
  else {
2717
- for (let i = 0; i < len;) {
2718
- let start = i, rtl = types[i++] == 2 /* R */;
2719
- while (i < len && rtl == (types[i] == 2 /* R */))
2720
- i++;
2721
- order.push(new BidiSpan(start, i, rtl ? 1 : 2));
2736
+ for (;;) {
2737
+ let { parent } = cView;
2738
+ if (!parent)
2739
+ return Decoration.none;
2740
+ if (parent instanceof LineView)
2741
+ break;
2742
+ cView = parent;
2722
2743
  }
2744
+ from = cView.posAtStart;
2745
+ to = from + cView.length;
2746
+ topNode = cView.dom;
2723
2747
  }
2724
- return order;
2748
+ let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2749
+ let { state } = view, text = topNode.nodeType == 3 ? topNode.nodeValue :
2750
+ new DOMReader([], view).readRange(topNode.firstChild, null).text;
2751
+ if (newTo - newFrom < text.length) {
2752
+ if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2753
+ newTo = newFrom + text.length;
2754
+ else if (state.sliceDoc(Math.max(0, newTo - text.length), newTo) == text)
2755
+ newFrom = newTo - text.length;
2756
+ else
2757
+ return Decoration.none;
2758
+ }
2759
+ else if (state.sliceDoc(newFrom, newTo) != text) {
2760
+ return Decoration.none;
2761
+ }
2762
+ return Decoration.set(Decoration.replace({ widget: new CompositionWidget(topNode, textNode) }).range(newFrom, newTo));
2725
2763
  }
2726
- function trivialOrder(length) {
2727
- return [new BidiSpan(0, length, 0)];
2764
+ class CompositionWidget extends WidgetType {
2765
+ constructor(top, text) {
2766
+ super();
2767
+ this.top = top;
2768
+ this.text = text;
2769
+ }
2770
+ eq(other) { return this.top == other.top && this.text == other.text; }
2771
+ toDOM() { return this.top; }
2772
+ ignoreEvent() { return false; }
2773
+ get customView() { return CompositionView; }
2728
2774
  }
2729
- let movedOver = "";
2730
- function moveVisually(line, order, dir, start, forward) {
2731
- var _a;
2732
- let startIndex = start.head - line.from, spanI = -1;
2733
- if (startIndex == 0) {
2734
- if (!forward || !line.length)
2735
- return null;
2736
- if (order[0].level != dir) {
2737
- startIndex = order[0].side(false, dir);
2738
- spanI = 0;
2775
+ function nearbyTextNode(node, offset, side) {
2776
+ for (;;) {
2777
+ if (node.nodeType == 3)
2778
+ return node;
2779
+ if (node.nodeType == 1 && offset > 0 && side <= 0) {
2780
+ node = node.childNodes[offset - 1];
2781
+ offset = maxOffset(node);
2739
2782
  }
2740
- }
2741
- else if (startIndex == line.length) {
2742
- if (forward)
2783
+ else if (node.nodeType == 1 && offset < node.childNodes.length && side >= 0) {
2784
+ node = node.childNodes[offset];
2785
+ offset = 0;
2786
+ }
2787
+ else {
2743
2788
  return null;
2744
- let last = order[order.length - 1];
2745
- if (last.level != dir) {
2746
- startIndex = last.side(true, dir);
2747
- spanI = order.length - 1;
2748
2789
  }
2749
2790
  }
2750
- if (spanI < 0)
2751
- spanI = BidiSpan.find(order, startIndex, (_a = start.bidiLevel) !== null && _a !== void 0 ? _a : -1, start.assoc);
2752
- let span = order[spanI];
2753
- // End of span. (But not end of line--that was checked for above.)
2754
- if (startIndex == span.side(forward, dir)) {
2755
- span = order[spanI += forward ? 1 : -1];
2756
- startIndex = span.side(!forward, dir);
2791
+ }
2792
+ function nextToUneditable(node, offset) {
2793
+ if (node.nodeType != 1)
2794
+ return 0;
2795
+ return (offset && node.childNodes[offset - 1].contentEditable == "false" ? 1 /* Before */ : 0) |
2796
+ (offset < node.childNodes.length && node.childNodes[offset].contentEditable == "false" ? 2 /* After */ : 0);
2797
+ }
2798
+ class DecorationComparator$1 {
2799
+ constructor() {
2800
+ this.changes = [];
2757
2801
  }
2758
- let indexForward = forward == (span.dir == dir);
2759
- let nextIndex = findClusterBreak(line.text, startIndex, indexForward);
2760
- movedOver = line.text.slice(Math.min(startIndex, nextIndex), Math.max(startIndex, nextIndex));
2761
- if (nextIndex != span.side(forward, dir))
2762
- return EditorSelection.cursor(nextIndex + line.from, indexForward ? -1 : 1, span.level);
2763
- let nextSpan = spanI == (forward ? order.length - 1 : 0) ? null : order[spanI + (forward ? 1 : -1)];
2764
- if (!nextSpan && span.level != dir)
2765
- return EditorSelection.cursor(forward ? line.to : line.from, forward ? -1 : 1, dir);
2766
- if (nextSpan && nextSpan.level < span.level)
2767
- return EditorSelection.cursor(nextSpan.side(!forward, dir) + line.from, forward ? 1 : -1, nextSpan.level);
2768
- return EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
2802
+ compareRange(from, to) { addRange(from, to, this.changes); }
2803
+ comparePoint(from, to) { addRange(from, to, this.changes); }
2804
+ }
2805
+ function findChangedDeco(a, b, diff) {
2806
+ let comp = new DecorationComparator$1;
2807
+ RangeSet.compare(a, b, diff, comp);
2808
+ return comp.changes;
2809
+ }
2810
+ function inUneditable(node, inside) {
2811
+ for (let cur = node; cur && cur != inside; cur = cur.assignedSlot || cur.parentNode) {
2812
+ if (cur.nodeType == 1 && cur.contentEditable == 'false') {
2813
+ return true;
2814
+ }
2815
+ }
2816
+ return false;
2769
2817
  }
2770
2818
 
2771
2819
  function groupAt(state, pos, bias = 1) {
@@ -2903,21 +2951,29 @@ function domPosInText(node, x, y) {
2903
2951
  function posAtCoords(view, { x, y }, precise, bias = -1) {
2904
2952
  var _a;
2905
2953
  let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
2906
- let halfLine = view.defaultLineHeight / 2;
2907
- let block, yOffset = y - docTop;
2908
- for (let bounced = false;;) {
2954
+ let block, yOffset = y - docTop, { docHeight } = view.viewState;
2955
+ if (yOffset < 0 || yOffset > docHeight) {
2956
+ if (precise)
2957
+ return null;
2958
+ yOffset = yOffset < 0 ? 0 : docHeight;
2959
+ }
2960
+ // Scan for a text block near the queried y position
2961
+ for (let halfLine = view.defaultLineHeight / 2, bounced = false;;) {
2909
2962
  block = view.elementAtHeight(yOffset);
2910
- if (block.top > yOffset || block.bottom < yOffset) {
2911
- bias = block.top > yOffset ? -1 : 1;
2912
- yOffset = Math.min(block.bottom - halfLine, Math.max(block.top + halfLine, yOffset));
2963
+ if (block.type == BlockType.Text)
2964
+ break;
2965
+ for (;;) {
2966
+ // Move the y position out of this block
2967
+ yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2968
+ if (yOffset >= 0 && yOffset <= docHeight)
2969
+ break;
2970
+ // If the document consists entirely of replaced widgets, we
2971
+ // won't find a text block, so return 0
2913
2972
  if (bounced)
2914
2973
  return precise ? null : 0;
2915
- else
2916
- bounced = true;
2974
+ bounced = true;
2975
+ bias = -bias;
2917
2976
  }
2918
- if (block.type == BlockType.Text)
2919
- break;
2920
- yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2921
2977
  }
2922
2978
  y = docTop + yOffset;
2923
2979
  let lineStart = block.from;
@@ -3258,10 +3314,10 @@ class InputState {
3258
3314
  return (event.type == "keydown" && event.keyCode != 229) ||
3259
3315
  event.type == "compositionend" && !browser.ios;
3260
3316
  }
3261
- startMouseSelection(view, event, style) {
3317
+ startMouseSelection(mouseSelection) {
3262
3318
  if (this.mouseSelection)
3263
3319
  this.mouseSelection.destroy();
3264
- this.mouseSelection = new MouseSelection(this, view, event, style);
3320
+ this.mouseSelection = mouseSelection;
3265
3321
  }
3266
3322
  update(update) {
3267
3323
  if (this.mouseSelection)
@@ -3282,10 +3338,10 @@ const PendingKeys = [
3282
3338
  // Key codes for modifier keys
3283
3339
  const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
3284
3340
  class MouseSelection {
3285
- constructor(inputState, view, startEvent, style) {
3286
- this.inputState = inputState;
3341
+ constructor(view, startEvent, style, mustSelect) {
3287
3342
  this.view = view;
3288
3343
  this.style = style;
3344
+ this.mustSelect = mustSelect;
3289
3345
  this.lastEvent = startEvent;
3290
3346
  let doc = view.contentDOM.ownerDocument;
3291
3347
  doc.addEventListener("mousemove", this.move = this.move.bind(this));
@@ -3319,16 +3375,18 @@ class MouseSelection {
3319
3375
  let doc = this.view.contentDOM.ownerDocument;
3320
3376
  doc.removeEventListener("mousemove", this.move);
3321
3377
  doc.removeEventListener("mouseup", this.up);
3322
- this.inputState.mouseSelection = null;
3378
+ this.view.inputState.mouseSelection = null;
3323
3379
  }
3324
3380
  select(event) {
3325
3381
  let selection = this.style.get(event, this.extend, this.multiple);
3326
- if (!selection.eq(this.view.state.selection) || selection.main.assoc != this.view.state.selection.main.assoc)
3382
+ if (this.mustSelect || !selection.eq(this.view.state.selection) ||
3383
+ selection.main.assoc != this.view.state.selection.main.assoc)
3327
3384
  this.view.dispatch({
3328
3385
  selection,
3329
3386
  userEvent: "select.pointer",
3330
3387
  scrollIntoView: true
3331
3388
  });
3389
+ this.mustSelect = false;
3332
3390
  }
3333
3391
  update(update) {
3334
3392
  if (update.docChanged && this.dragging)
@@ -3447,9 +3505,10 @@ handlers.mousedown = (view, event) => {
3447
3505
  if (!style && event.button == 0)
3448
3506
  style = basicMouseSelection(view, event);
3449
3507
  if (style) {
3450
- if (view.root.activeElement != view.contentDOM)
3508
+ let mustFocus = view.root.activeElement != view.contentDOM;
3509
+ if (mustFocus)
3451
3510
  view.observer.ignore(() => focusPreventScroll(view.contentDOM));
3452
- view.inputState.startMouseSelection(view, event, style);
3511
+ view.inputState.startMouseSelection(new MouseSelection(view, event, style, mustFocus));
3453
3512
  }
3454
3513
  };
3455
3514
  function rangeForClick(view, pos, bias, type) {
@@ -4164,12 +4223,12 @@ class HeightMapBranch extends HeightMap {
4164
4223
  get break() { return this.flags & 1 /* Break */; }
4165
4224
  blockAt(height, doc, top, offset) {
4166
4225
  let mid = top + this.left.height;
4167
- return height < mid || this.right.height == 0 ? this.left.blockAt(height, doc, top, offset)
4226
+ return height < mid ? this.left.blockAt(height, doc, top, offset)
4168
4227
  : this.right.blockAt(height, doc, mid, offset + this.left.length + this.break);
4169
4228
  }
4170
4229
  lineAt(value, type, doc, top, offset) {
4171
4230
  let rightTop = top + this.left.height, rightOffset = offset + this.left.length + this.break;
4172
- let left = type == QueryType.ByHeight ? value < rightTop || this.right.height == 0 : value < rightOffset;
4231
+ let left = type == QueryType.ByHeight ? value < rightTop : value < rightOffset;
4173
4232
  let base = left ? this.left.lineAt(value, type, doc, top, offset)
4174
4233
  : this.right.lineAt(value, type, doc, rightTop, rightOffset);
4175
4234
  if (this.break || (left ? base.to < rightOffset : base.from > rightOffset))
@@ -4310,7 +4369,9 @@ class NodeBuilder {
4310
4369
  }
4311
4370
  point(from, to, deco) {
4312
4371
  if (from < to || deco.heightRelevant) {
4313
- let height = deco.widget ? Math.max(0, deco.widget.estimatedHeight) : 0;
4372
+ let height = deco.widget ? deco.widget.estimatedHeight : 0;
4373
+ if (height < 0)
4374
+ height = this.oracle.lineHeight;
4314
4375
  let len = to - from;
4315
4376
  if (deco.block) {
4316
4377
  this.addBlock(new HeightMapBlock(len, height, deco.type));
@@ -4438,8 +4499,8 @@ function visiblePixelRange(dom, paddingTop) {
4438
4499
  break;
4439
4500
  }
4440
4501
  }
4441
- return { left: left - rect.left, right: right - rect.left,
4442
- top: top - (rect.top + paddingTop), bottom: bottom - (rect.top + paddingTop) };
4502
+ return { left: left - rect.left, right: Math.max(left, right) - rect.left,
4503
+ top: top - (rect.top + paddingTop), bottom: Math.max(top, bottom) - (rect.top + paddingTop) };
4443
4504
  }
4444
4505
  // Line gaps are placeholder widgets used to hide pieces of overlong
4445
4506
  // lines within the viewport, as a kludge to keep the editor
@@ -4666,7 +4727,7 @@ class ViewState {
4666
4727
  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);
4667
4728
  // If scrollTarget is given, make sure the viewport includes that position
4668
4729
  if (scrollTarget) {
4669
- let { head } = scrollTarget.range, viewHeight = visibleBottom - visibleTop;
4730
+ let { head } = scrollTarget.range, viewHeight = this.editorHeight;
4670
4731
  if (head < viewport.from || head > viewport.to) {
4671
4732
  let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4672
4733
  if (scrollTarget.center)
@@ -4687,6 +4748,8 @@ class ViewState {
4687
4748
  // Checks if a given viewport covers the visible part of the
4688
4749
  // document and not too much beyond that.
4689
4750
  viewportIsAppropriate({ from, to }, bias = 0) {
4751
+ if (!this.inView)
4752
+ return true;
4690
4753
  let { top } = this.heightMap.lineAt(from, QueryType.ByPos, this.state.doc, 0, 0);
4691
4754
  let { bottom } = this.heightMap.lineAt(to, QueryType.ByPos, this.state.doc, 0, 0);
4692
4755
  let { visibleTop, visibleBottom } = this;
@@ -4793,8 +4856,11 @@ class ViewState {
4793
4856
  elementAtHeight(height) {
4794
4857
  return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height), this.state.doc, 0, 0), this.scaler);
4795
4858
  }
4859
+ get docHeight() {
4860
+ return this.scaler.toDOM(this.heightMap.height);
4861
+ }
4796
4862
  get contentHeight() {
4797
- return this.scaler.toDOM(this.heightMap.height) + this.paddingTop + this.paddingBottom;
4863
+ return this.docHeight + this.paddingTop + this.paddingBottom;
4798
4864
  }
4799
4865
  }
4800
4866
  class Viewport {
@@ -5048,7 +5114,8 @@ const baseTheme = /*@__PURE__*/buildTheme("." + baseThemeID, {
5048
5114
  },
5049
5115
  ".cm-placeholder": {
5050
5116
  color: "#888",
5051
- display: "inline-block"
5117
+ display: "inline-block",
5118
+ verticalAlign: "top",
5052
5119
  },
5053
5120
  ".cm-button": {
5054
5121
  verticalAlign: "middle",
@@ -5346,7 +5413,7 @@ class DOMObserver {
5346
5413
  this.onChange(from, to, typeOver);
5347
5414
  // The view wasn't updated
5348
5415
  if (this.view.state == startState)
5349
- this.view.docView.reset(newSel);
5416
+ this.view.update([]);
5350
5417
  }
5351
5418
  readMutation(rec) {
5352
5419
  let cView = this.view.docView.nearest(rec.target);
@@ -5477,11 +5544,22 @@ function applyDOMChange(view, start, end, typeOver) {
5477
5544
  };
5478
5545
  if (change) {
5479
5546
  let startState = view.state;
5547
+ if (browser.ios && view.inputState.flushIOSKey(view))
5548
+ return;
5480
5549
  // Android browsers don't fire reasonable key events for enter,
5481
5550
  // backspace, or delete. So this detects changes that look like
5482
5551
  // they're caused by those keys, and reinterprets them as key
5483
- // events.
5484
- if (browser.ios && view.inputState.flushIOSKey(view))
5552
+ // events. (Some of these keys are also handled by beforeinput
5553
+ // events and the pendingAndroidKey mechanism, but that's not
5554
+ // reliable in all situations.)
5555
+ if (browser.android &&
5556
+ ((change.from == sel.from && change.to == sel.to &&
5557
+ change.insert.length == 1 && change.insert.lines == 2 &&
5558
+ dispatchKey(view.contentDOM, "Enter", 13)) ||
5559
+ (change.from == sel.from - 1 && change.to == sel.to && change.insert.length == 0 &&
5560
+ dispatchKey(view.contentDOM, "Backspace", 8)) ||
5561
+ (change.from == sel.from && change.to == sel.to + 1 && change.insert.length == 0 &&
5562
+ dispatchKey(view.contentDOM, "Delete", 46))))
5485
5563
  return;
5486
5564
  let text = change.insert.toString();
5487
5565
  if (view.state.facet(inputHandler).some(h => h(view, change.from, change.to, text)))
@@ -5554,76 +5632,6 @@ function findDiff(a, b, preferredPos, preferredSide) {
5554
5632
  }
5555
5633
  return { from, toA, toB };
5556
5634
  }
5557
- class DOMReader {
5558
- constructor(points, view) {
5559
- this.points = points;
5560
- this.view = view;
5561
- this.text = "";
5562
- this.lineBreak = view.state.lineBreak;
5563
- }
5564
- readRange(start, end) {
5565
- if (!start)
5566
- return;
5567
- let parent = start.parentNode;
5568
- for (let cur = start;;) {
5569
- this.findPointBefore(parent, cur);
5570
- this.readNode(cur);
5571
- let next = cur.nextSibling;
5572
- if (next == end)
5573
- break;
5574
- let view = ContentView.get(cur), nextView = ContentView.get(next);
5575
- if (view && nextView ? view.breakAfter :
5576
- (view ? view.breakAfter : isBlockElement(cur)) ||
5577
- (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
5578
- this.text += this.lineBreak;
5579
- cur = next;
5580
- }
5581
- this.findPointBefore(parent, end);
5582
- }
5583
- readNode(node) {
5584
- if (node.cmIgnore)
5585
- return;
5586
- let view = ContentView.get(node);
5587
- let fromView = view && view.overrideDOMText;
5588
- let text;
5589
- if (fromView != null)
5590
- text = fromView.sliceString(0, undefined, this.lineBreak);
5591
- else if (node.nodeType == 3)
5592
- text = node.nodeValue;
5593
- else if (node.nodeName == "BR")
5594
- text = node.nextSibling ? this.lineBreak : "";
5595
- else if (node.nodeType == 1)
5596
- this.readRange(node.firstChild, null);
5597
- if (text != null) {
5598
- this.findPointIn(node, text.length);
5599
- this.text += text;
5600
- // Chrome inserts two newlines when pressing shift-enter at the
5601
- // end of a line. This drops one of those.
5602
- if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
5603
- this.text = this.text.slice(0, -1);
5604
- }
5605
- }
5606
- findPointBefore(node, next) {
5607
- for (let point of this.points)
5608
- if (point.node == node && node.childNodes[point.offset] == next)
5609
- point.pos = this.text.length;
5610
- }
5611
- findPointIn(node, maxLen) {
5612
- for (let point of this.points)
5613
- if (point.node == node)
5614
- point.pos = this.text.length + Math.min(point.offset, maxLen);
5615
- }
5616
- }
5617
- function isBlockElement(node) {
5618
- return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
5619
- }
5620
- class DOMPoint {
5621
- constructor(node, offset) {
5622
- this.node = node;
5623
- this.offset = offset;
5624
- this.pos = -1;
5625
- }
5626
- }
5627
5635
  function selectionPoints(view) {
5628
5636
  let result = [];
5629
5637
  if (view.root.activeElement != view.contentDOM)
@@ -5706,7 +5714,9 @@ class EditorView {
5706
5714
  this.dispatch = this.dispatch.bind(this);
5707
5715
  this.root = (config.root || getRoot(config.parent) || document);
5708
5716
  this.viewState = new ViewState(config.state || EditorState.create());
5709
- this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec).update(this));
5717
+ this.plugins = this.state.facet(viewPlugin).map(spec => new PluginInstance(spec));
5718
+ for (let plugin of this.plugins)
5719
+ plugin.update(this);
5710
5720
  this.observer = new DOMObserver(this, (from, to, typeOver) => {
5711
5721
  applyDOMChange(this, from, to, typeOver);
5712
5722
  }, event => {
@@ -5843,8 +5853,10 @@ class EditorView {
5843
5853
  for (let plugin of this.plugins)
5844
5854
  plugin.destroy(this);
5845
5855
  this.viewState = new ViewState(newState);
5846
- this.plugins = newState.facet(viewPlugin).map(spec => new PluginInstance(spec).update(this));
5856
+ this.plugins = newState.facet(viewPlugin).map(spec => new PluginInstance(spec));
5847
5857
  this.pluginMap.clear();
5858
+ for (let plugin of this.plugins)
5859
+ plugin.update(this);
5848
5860
  this.docView = new DocView(this);
5849
5861
  this.inputState.ensureHandlers(this);
5850
5862
  this.mountStyles();
@@ -5905,7 +5917,9 @@ class EditorView {
5905
5917
  if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
5906
5918
  break;
5907
5919
  if (i > 5) {
5908
- console.warn(this.measureRequests.length ? "Measure loop restarted more than 5 times" : "Viewport failed to stabilize");
5920
+ console.warn(this.measureRequests.length
5921
+ ? "Measure loop restarted more than 5 times"
5922
+ : "Viewport failed to stabilize");
5909
5923
  break;
5910
5924
  }
5911
5925
  let measuring = [];
@@ -5951,7 +5965,8 @@ class EditorView {
5951
5965
  }
5952
5966
  if (redrawn)
5953
5967
  this.docView.updateSelection(true);
5954
- if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
5968
+ if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
5969
+ this.measureRequests.length == 0)
5955
5970
  break;
5956
5971
  }
5957
5972
  }
@@ -6243,6 +6258,11 @@ class EditorView {
6243
6258
  Find the DOM parent node and offset (child offset if `node` is
6244
6259
  an element, character offset when it is a text node) at the
6245
6260
  given document position.
6261
+
6262
+ Note that for positions that aren't currently in
6263
+ `visibleRanges`, the resulting DOM position isn't necessarily
6264
+ meaningful (it may just point before or after a placeholder
6265
+ element).
6246
6266
  */
6247
6267
  domAtPos(pos) {
6248
6268
  return this.docView.domAtPos(pos);