@codemirror/view 0.19.28 → 0.19.32

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.cjs CHANGED
@@ -658,6 +658,7 @@ class TextView extends ContentView {
658
658
  split(from) {
659
659
  let result = new TextView(this.text.slice(from));
660
660
  this.text = this.text.slice(0, from);
661
+ this.markDirty();
661
662
  return result;
662
663
  }
663
664
  localPosFromDOM(node, offset) {
@@ -2254,6 +2255,78 @@ function moveVisually(line, order, dir, start, forward) {
2254
2255
  return state.EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
2255
2256
  }
2256
2257
 
2258
+ class DOMReader {
2259
+ constructor(points, view) {
2260
+ this.points = points;
2261
+ this.view = view;
2262
+ this.text = "";
2263
+ this.lineBreak = view.state.lineBreak;
2264
+ }
2265
+ readRange(start, end) {
2266
+ if (!start)
2267
+ return this;
2268
+ let parent = start.parentNode;
2269
+ for (let cur = start;;) {
2270
+ this.findPointBefore(parent, cur);
2271
+ this.readNode(cur);
2272
+ let next = cur.nextSibling;
2273
+ if (next == end)
2274
+ break;
2275
+ let view = ContentView.get(cur), nextView = ContentView.get(next);
2276
+ if (view && nextView ? view.breakAfter :
2277
+ (view ? view.breakAfter : isBlockElement(cur)) ||
2278
+ (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
2279
+ this.text += this.lineBreak;
2280
+ cur = next;
2281
+ }
2282
+ this.findPointBefore(parent, end);
2283
+ return this;
2284
+ }
2285
+ readNode(node) {
2286
+ if (node.cmIgnore)
2287
+ return;
2288
+ let view = ContentView.get(node);
2289
+ let fromView = view && view.overrideDOMText;
2290
+ let text;
2291
+ if (fromView != null)
2292
+ text = fromView.sliceString(0, undefined, this.lineBreak);
2293
+ else if (node.nodeType == 3)
2294
+ text = node.nodeValue;
2295
+ else if (node.nodeName == "BR")
2296
+ text = node.nextSibling ? this.lineBreak : "";
2297
+ else if (node.nodeType == 1)
2298
+ this.readRange(node.firstChild, null);
2299
+ if (text != null) {
2300
+ this.findPointIn(node, text.length);
2301
+ this.text += text;
2302
+ // Chrome inserts two newlines when pressing shift-enter at the
2303
+ // end of a line. This drops one of those.
2304
+ if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
2305
+ this.text = this.text.slice(0, -1);
2306
+ }
2307
+ }
2308
+ findPointBefore(node, next) {
2309
+ for (let point of this.points)
2310
+ if (point.node == node && node.childNodes[point.offset] == next)
2311
+ point.pos = this.text.length;
2312
+ }
2313
+ findPointIn(node, maxLen) {
2314
+ for (let point of this.points)
2315
+ if (point.node == node)
2316
+ point.pos = this.text.length + Math.min(point.offset, maxLen);
2317
+ }
2318
+ }
2319
+ function isBlockElement(node) {
2320
+ return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
2321
+ }
2322
+ class DOMPoint {
2323
+ constructor(node, offset) {
2324
+ this.node = node;
2325
+ this.offset = offset;
2326
+ this.pos = -1;
2327
+ }
2328
+ }
2329
+
2257
2330
  class DocView extends ContentView {
2258
2331
  constructor(view) {
2259
2332
  super();
@@ -2303,7 +2376,7 @@ class DocView extends ContentView {
2303
2376
  }
2304
2377
  if (this.view.inputState.composing < 0)
2305
2378
  this.compositionDeco = Decoration.none;
2306
- else if (update.transactions.length)
2379
+ else if (update.transactions.length || this.dirty)
2307
2380
  this.compositionDeco = computeCompositionDeco(this.view, update.changes);
2308
2381
  // When the DOM nodes around the selection are moved to another
2309
2382
  // parent, Chrome sometimes reports a different selection through
@@ -2326,16 +2399,6 @@ class DocView extends ContentView {
2326
2399
  return true;
2327
2400
  }
2328
2401
  }
2329
- reset(sel) {
2330
- if (this.dirty) {
2331
- this.view.observer.ignore(() => this.view.docView.sync());
2332
- this.dirty = 0 /* Not */;
2333
- this.updateSelection(true);
2334
- }
2335
- else {
2336
- this.updateSelection();
2337
- }
2338
- }
2339
2402
  // Used by update and the constructor do perform the actual DOM
2340
2403
  // update
2341
2404
  updateInner(changes, deco, oldLength) {
@@ -2411,7 +2474,8 @@ class DocView extends ContentView {
2411
2474
  // inside an uneditable node, and not bring it back when we
2412
2475
  // move the cursor to its proper position. This tries to
2413
2476
  // restore the keyboard by cycling focus.
2414
- if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, this.dom)) {
2477
+ if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) &&
2478
+ inUneditable(domSel.focusNode, this.dom)) {
2415
2479
  this.dom.blur();
2416
2480
  this.dom.focus({ preventScroll: true });
2417
2481
  }
@@ -2454,7 +2518,7 @@ class DocView extends ContentView {
2454
2518
  this.impreciseHead = head.precise ? null : new DOMPos(domSel.focusNode, domSel.focusOffset);
2455
2519
  }
2456
2520
  enforceCursorAssoc() {
2457
- if (this.view.composing)
2521
+ if (this.compositionDeco.size)
2458
2522
  return;
2459
2523
  let cursor = this.view.state.selection.main;
2460
2524
  let sel = getSelection(this.root);
@@ -2679,7 +2743,8 @@ function computeCompositionDeco(view, changes) {
2679
2743
  topNode = cView.dom;
2680
2744
  }
2681
2745
  let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
2682
- let text = textNode.nodeValue, { state } = view;
2746
+ let { state } = view, text = topNode.nodeType == 3 ? topNode.nodeValue :
2747
+ new DOMReader([], view).readRange(topNode.firstChild, null).text;
2683
2748
  if (newTo - newFrom < text.length) {
2684
2749
  if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
2685
2750
  newTo = newFrom + text.length;
@@ -2883,21 +2948,29 @@ function domPosInText(node, x, y) {
2883
2948
  function posAtCoords(view, { x, y }, precise, bias = -1) {
2884
2949
  var _a;
2885
2950
  let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
2886
- let halfLine = view.defaultLineHeight / 2;
2887
- let block, yOffset = y - docTop;
2888
- for (let bounced = false;;) {
2951
+ let block, yOffset = y - docTop, { docHeight } = view.viewState;
2952
+ if (yOffset < 0 || yOffset > docHeight) {
2953
+ if (precise)
2954
+ return null;
2955
+ yOffset = yOffset < 0 ? 0 : docHeight;
2956
+ }
2957
+ // Scan for a text block near the queried y position
2958
+ for (let halfLine = view.defaultLineHeight / 2, bounced = false;;) {
2889
2959
  block = view.elementAtHeight(yOffset);
2890
- if (block.top > yOffset || block.bottom < yOffset) {
2891
- bias = block.top > yOffset ? -1 : 1;
2892
- yOffset = Math.min(block.bottom - halfLine, Math.max(block.top + halfLine, yOffset));
2960
+ if (block.type == exports.BlockType.Text)
2961
+ break;
2962
+ for (;;) {
2963
+ // Move the y position out of this block
2964
+ yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2965
+ if (yOffset >= 0 && yOffset <= docHeight)
2966
+ break;
2967
+ // If the document consists entirely of replaced widgets, we
2968
+ // won't find a text block, so return 0
2893
2969
  if (bounced)
2894
2970
  return precise ? null : 0;
2895
- else
2896
- bounced = true;
2971
+ bounced = true;
2972
+ bias = -bias;
2897
2973
  }
2898
- if (block.type == exports.BlockType.Text)
2899
- break;
2900
- yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2901
2974
  }
2902
2975
  y = docTop + yOffset;
2903
2976
  let lineStart = block.from;
@@ -3055,14 +3128,6 @@ class InputState {
3055
3128
  constructor(view) {
3056
3129
  this.lastKeyCode = 0;
3057
3130
  this.lastKeyTime = 0;
3058
- // On Chrome Android, backspace near widgets is just completely
3059
- // broken, and there are no key events, so we need to handle the
3060
- // beforeinput event. Deleting stuff will often create a flurry of
3061
- // events, and interrupting it before it is done just makes
3062
- // subsequent events even more broken, so again, we hold off doing
3063
- // anything until the browser is finished with whatever it is trying
3064
- // to do.
3065
- this.pendingAndroidKey = undefined;
3066
3131
  // On iOS, some keys need to have their default behavior happen
3067
3132
  // (after which we retroactively handle them and reset the DOM) to
3068
3133
  // avoid messing up the virtual keyboard state.
@@ -3131,22 +3196,15 @@ class InputState {
3131
3196
  }
3132
3197
  runCustomHandlers(type, view, event) {
3133
3198
  for (let set of this.customHandlers) {
3134
- let handler = set.handlers[type], handled = false;
3199
+ let handler = set.handlers[type];
3135
3200
  if (handler) {
3136
3201
  try {
3137
- handled = handler.call(set.plugin, event, view);
3202
+ if (handler.call(set.plugin, event, view) || event.defaultPrevented)
3203
+ return true;
3138
3204
  }
3139
3205
  catch (e) {
3140
3206
  logException(view.state, e);
3141
3207
  }
3142
- if (handled || event.defaultPrevented) {
3143
- // Chrome for Android often applies a bunch of nonsensical
3144
- // DOM changes after an enter press, even when
3145
- // preventDefault-ed. This tries to ignore those.
3146
- if (browser.android && type == "keydown" && event.keyCode == 13)
3147
- view.observer.flushSoon();
3148
- return true;
3149
- }
3150
3208
  }
3151
3209
  }
3152
3210
  return false;
@@ -3170,6 +3228,16 @@ class InputState {
3170
3228
  this.lastKeyTime = Date.now();
3171
3229
  if (this.screenKeyEvent(view, event))
3172
3230
  return true;
3231
+ // Chrome for Android usually doesn't fire proper key events, but
3232
+ // occasionally does, usually surrounded by a bunch of complicated
3233
+ // composition changes. When an enter or backspace key event is
3234
+ // seen, hold off on handling DOM events for a bit, and then
3235
+ // dispatch it.
3236
+ if (browser.android && browser.chrome && !event.synthetic &&
3237
+ (event.keyCode == 13 || event.keyCode == 8)) {
3238
+ view.observer.delayAndroidKey(event.key, event.keyCode);
3239
+ return true;
3240
+ }
3173
3241
  // Prevent the default behavior of Enter on iOS makes the
3174
3242
  // virtual keyboard get stuck in the wrong (lowercase)
3175
3243
  // state. So we let it go through, and then, in
@@ -3191,24 +3259,6 @@ class InputState {
3191
3259
  this.pendingIOSKey = undefined;
3192
3260
  return dispatchKey(view.contentDOM, key.key, key.keyCode);
3193
3261
  }
3194
- // This causes the DOM observer to pause for a bit, and sets an
3195
- // animation frame (which seems the most reliable way to detect
3196
- // 'Chrome is done flailing about messing with the DOM') to fire a
3197
- // fake key event and re-sync the view again.
3198
- setPendingAndroidKey(view, pending) {
3199
- this.pendingAndroidKey = pending;
3200
- requestAnimationFrame(() => {
3201
- let key = this.pendingAndroidKey;
3202
- if (!key)
3203
- return;
3204
- this.pendingAndroidKey = undefined;
3205
- view.observer.processRecords();
3206
- let startState = view.state;
3207
- dispatchKey(view.contentDOM, key.key, key.keyCode);
3208
- if (view.state == startState)
3209
- view.docView.reset(true);
3210
- });
3211
- }
3212
3262
  ignoreDuringComposition(event) {
3213
3263
  if (!/^key/.test(event.type))
3214
3264
  return false;
@@ -3238,10 +3288,10 @@ class InputState {
3238
3288
  return (event.type == "keydown" && event.keyCode != 229) ||
3239
3289
  event.type == "compositionend" && !browser.ios;
3240
3290
  }
3241
- startMouseSelection(view, event, style) {
3291
+ startMouseSelection(mouseSelection) {
3242
3292
  if (this.mouseSelection)
3243
3293
  this.mouseSelection.destroy();
3244
- this.mouseSelection = new MouseSelection(this, view, event, style);
3294
+ this.mouseSelection = mouseSelection;
3245
3295
  }
3246
3296
  update(update) {
3247
3297
  if (this.mouseSelection)
@@ -3262,10 +3312,10 @@ const PendingKeys = [
3262
3312
  // Key codes for modifier keys
3263
3313
  const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
3264
3314
  class MouseSelection {
3265
- constructor(inputState, view, startEvent, style) {
3266
- this.inputState = inputState;
3315
+ constructor(view, startEvent, style, mustSelect) {
3267
3316
  this.view = view;
3268
3317
  this.style = style;
3318
+ this.mustSelect = mustSelect;
3269
3319
  this.lastEvent = startEvent;
3270
3320
  let doc = view.contentDOM.ownerDocument;
3271
3321
  doc.addEventListener("mousemove", this.move = this.move.bind(this));
@@ -3299,16 +3349,18 @@ class MouseSelection {
3299
3349
  let doc = this.view.contentDOM.ownerDocument;
3300
3350
  doc.removeEventListener("mousemove", this.move);
3301
3351
  doc.removeEventListener("mouseup", this.up);
3302
- this.inputState.mouseSelection = null;
3352
+ this.view.inputState.mouseSelection = null;
3303
3353
  }
3304
3354
  select(event) {
3305
3355
  let selection = this.style.get(event, this.extend, this.multiple);
3306
- if (!selection.eq(this.view.state.selection) || selection.main.assoc != this.view.state.selection.main.assoc)
3356
+ if (this.mustSelect || !selection.eq(this.view.state.selection) ||
3357
+ selection.main.assoc != this.view.state.selection.main.assoc)
3307
3358
  this.view.dispatch({
3308
3359
  selection,
3309
3360
  userEvent: "select.pointer",
3310
3361
  scrollIntoView: true
3311
3362
  });
3363
+ this.mustSelect = false;
3312
3364
  }
3313
3365
  update(update) {
3314
3366
  if (update.docChanged && this.dragging)
@@ -3427,9 +3479,10 @@ handlers.mousedown = (view, event) => {
3427
3479
  if (!style && event.button == 0)
3428
3480
  style = basicMouseSelection(view, event);
3429
3481
  if (style) {
3430
- if (view.root.activeElement != view.contentDOM)
3482
+ let mustFocus = view.root.activeElement != view.contentDOM;
3483
+ if (mustFocus)
3431
3484
  view.observer.ignore(() => focusPreventScroll(view.contentDOM));
3432
- view.inputState.startMouseSelection(view, event, style);
3485
+ view.inputState.startMouseSelection(new MouseSelection(view, event, style, mustFocus));
3433
3486
  }
3434
3487
  };
3435
3488
  function rangeForClick(view, pos, bias, type) {
@@ -3684,12 +3737,12 @@ handlers.compositionstart = handlers.compositionupdate = view => {
3684
3737
  if (view.inputState.compositionFirstChange == null)
3685
3738
  view.inputState.compositionFirstChange = true;
3686
3739
  if (view.inputState.composing < 0) {
3740
+ // FIXME possibly set a timeout to clear it again on Android
3741
+ view.inputState.composing = 0;
3687
3742
  if (view.docView.compositionDeco.size) {
3688
3743
  view.observer.flush();
3689
3744
  forceClearComposition(view, true);
3690
3745
  }
3691
- // FIXME possibly set a timeout to clear it again on Android
3692
- view.inputState.composing = 0;
3693
3746
  }
3694
3747
  };
3695
3748
  handlers.compositionend = view => {
@@ -3715,7 +3768,7 @@ handlers.beforeinput = (view, event) => {
3715
3768
  // seems to do nothing at all on Chrome).
3716
3769
  let pending;
3717
3770
  if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
3718
- view.inputState.setPendingAndroidKey(view, pending);
3771
+ view.observer.delayAndroidKey(pending.key, pending.keyCode);
3719
3772
  if (pending.key == "Backspace" || pending.key == "Delete") {
3720
3773
  let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
3721
3774
  setTimeout(() => {
@@ -4421,8 +4474,8 @@ function visiblePixelRange(dom, paddingTop) {
4421
4474
  break;
4422
4475
  }
4423
4476
  }
4424
- return { left: left - rect.left, right: right - rect.left,
4425
- top: top - (rect.top + paddingTop), bottom: bottom - (rect.top + paddingTop) };
4477
+ return { left: left - rect.left, right: Math.max(left, right) - rect.left,
4478
+ top: top - (rect.top + paddingTop), bottom: Math.max(top, bottom) - (rect.top + paddingTop) };
4426
4479
  }
4427
4480
  // Line gaps are placeholder widgets used to hide pieces of overlong
4428
4481
  // lines within the viewport, as a kludge to keep the editor
@@ -4551,9 +4604,9 @@ class ViewState {
4551
4604
  let updateLines = !update.changes.empty || (update.flags & 2 /* Height */) ||
4552
4605
  viewport.from != this.viewport.from || viewport.to != this.viewport.to;
4553
4606
  this.viewport = viewport;
4607
+ this.updateForViewport();
4554
4608
  if (updateLines)
4555
4609
  this.updateViewportLines();
4556
- this.updateForViewport();
4557
4610
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4558
4611
  this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
4559
4612
  update.flags |= this.computeVisibleRanges();
@@ -4585,7 +4638,12 @@ class ViewState {
4585
4638
  let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 } : visiblePixelRange(dom, this.paddingTop);
4586
4639
  let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
4587
4640
  this.pixelViewport = pixelViewport;
4588
- this.inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
4641
+ let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
4642
+ if (inView != this.inView) {
4643
+ this.inView = inView;
4644
+ if (inView)
4645
+ measureContent = true;
4646
+ }
4589
4647
  if (!this.inView)
4590
4648
  return 0;
4591
4649
  if (measureContent) {
@@ -4622,9 +4680,9 @@ class ViewState {
4622
4680
  this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to);
4623
4681
  if (viewportChange)
4624
4682
  this.viewport = this.getViewport(bias, this.scrollTarget);
4683
+ this.updateForViewport();
4625
4684
  if ((result & 2 /* Height */) || viewportChange)
4626
4685
  this.updateViewportLines();
4627
- this.updateForViewport();
4628
4686
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4629
4687
  this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
4630
4688
  result |= this.computeVisibleRanges();
@@ -4649,7 +4707,7 @@ class ViewState {
4649
4707
  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);
4650
4708
  // If scrollTarget is given, make sure the viewport includes that position
4651
4709
  if (scrollTarget) {
4652
- let { head } = scrollTarget.range, viewHeight = visibleBottom - visibleTop;
4710
+ let { head } = scrollTarget.range, viewHeight = this.editorHeight;
4653
4711
  if (head < viewport.from || head > viewport.to) {
4654
4712
  let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
4655
4713
  if (scrollTarget.center)
@@ -4670,6 +4728,8 @@ class ViewState {
4670
4728
  // Checks if a given viewport covers the visible part of the
4671
4729
  // document and not too much beyond that.
4672
4730
  viewportIsAppropriate({ from, to }, bias = 0) {
4731
+ if (!this.inView)
4732
+ return true;
4673
4733
  let { top } = this.heightMap.lineAt(from, QueryType.ByPos, this.state.doc, 0, 0);
4674
4734
  let { bottom } = this.heightMap.lineAt(to, QueryType.ByPos, this.state.doc, 0, 0);
4675
4735
  let { visibleTop, visibleBottom } = this;
@@ -4776,8 +4836,11 @@ class ViewState {
4776
4836
  elementAtHeight(height) {
4777
4837
  return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height), this.state.doc, 0, 0), this.scaler);
4778
4838
  }
4839
+ get docHeight() {
4840
+ return this.scaler.toDOM(this.heightMap.height);
4841
+ }
4779
4842
  get contentHeight() {
4780
- return this.scaler.toDOM(this.heightMap.height) + this.paddingTop + this.paddingBottom;
4843
+ return this.docHeight + this.paddingTop + this.paddingBottom;
4781
4844
  }
4782
4845
  }
4783
4846
  class Viewport {
@@ -5007,11 +5070,13 @@ const baseTheme = buildTheme("." + baseThemeID, {
5007
5070
  // recomputation.
5008
5071
  "@keyframes cm-blink": { "0%": {}, "50%": { visibility: "hidden" }, "100%": {} },
5009
5072
  "@keyframes cm-blink2": { "0%": {}, "50%": { visibility: "hidden" }, "100%": {} },
5010
- ".cm-cursor": {
5073
+ ".cm-cursor, .cm-dropCursor": {
5011
5074
  position: "absolute",
5012
5075
  borderLeft: "1.2px solid black",
5013
5076
  marginLeft: "-0.6px",
5014
5077
  pointerEvents: "none",
5078
+ },
5079
+ ".cm-cursor": {
5015
5080
  display: "none"
5016
5081
  },
5017
5082
  "&dark .cm-cursor": {
@@ -5099,6 +5164,7 @@ class DOMObserver {
5099
5164
  this.delayedFlush = -1;
5100
5165
  this.resizeTimeout = -1;
5101
5166
  this.queue = [];
5167
+ this.delayedAndroidKey = null;
5102
5168
  this.scrollTargets = [];
5103
5169
  this.intersection = null;
5104
5170
  this.resize = null;
@@ -5152,7 +5218,7 @@ class DOMObserver {
5152
5218
  this.intersection = new IntersectionObserver(entries => {
5153
5219
  if (this.parentCheck < 0)
5154
5220
  this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1000);
5155
- if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
5221
+ if (entries.length > 0 && (entries[entries.length - 1].intersectionRatio > 0) != this.intersecting) {
5156
5222
  this.intersecting = !this.intersecting;
5157
5223
  if (this.intersecting != this.view.inView)
5158
5224
  this.onScrollChanged(document.createEvent("Event"));
@@ -5182,7 +5248,7 @@ class DOMObserver {
5182
5248
  }
5183
5249
  }
5184
5250
  onSelectionChange(event) {
5185
- if (!this.readSelectionRange())
5251
+ if (!this.readSelectionRange() || this.delayedAndroidKey)
5186
5252
  return;
5187
5253
  let { view } = this, sel = this.selectionRange;
5188
5254
  if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
@@ -5278,6 +5344,32 @@ class DOMObserver {
5278
5344
  this.queue.length = 0;
5279
5345
  this.selectionChanged = false;
5280
5346
  }
5347
+ // Chrome Android, especially in combination with GBoard, not only
5348
+ // doesn't reliably fire regular key events, but also often
5349
+ // surrounds the effect of enter or backspace with a bunch of
5350
+ // composition events that, when interrupted, cause text duplication
5351
+ // or other kinds of corruption. This hack makes the editor back off
5352
+ // from handling DOM changes for a moment when such a key is
5353
+ // detected (via beforeinput or keydown), and then dispatches the
5354
+ // key event, throwing away the DOM changes if it gets handled.
5355
+ delayAndroidKey(key, keyCode) {
5356
+ if (!this.delayedAndroidKey)
5357
+ requestAnimationFrame(() => {
5358
+ let key = this.delayedAndroidKey;
5359
+ this.delayedAndroidKey = null;
5360
+ let startState = this.view.state;
5361
+ if (dispatchKey(this.view.contentDOM, key.key, key.keyCode))
5362
+ this.processRecords();
5363
+ else
5364
+ this.flush();
5365
+ if (this.view.state == startState)
5366
+ this.view.update([]);
5367
+ });
5368
+ // Since backspace beforeinput is sometimes signalled spuriously,
5369
+ // Enter always takes precedence.
5370
+ if (!this.delayedAndroidKey || key == "Enter")
5371
+ this.delayedAndroidKey = { key, keyCode };
5372
+ }
5281
5373
  flushSoon() {
5282
5374
  if (this.delayedFlush < 0)
5283
5375
  this.delayedFlush = window.setTimeout(() => { this.delayedFlush = -1; this.flush(); }, 20);
@@ -5314,13 +5406,13 @@ class DOMObserver {
5314
5406
  }
5315
5407
  // Apply pending changes, if any
5316
5408
  flush(readSelection = true) {
5317
- if (readSelection)
5318
- this.readSelectionRange();
5319
5409
  // Completely hold off flushing when pending keys are set—the code
5320
5410
  // managing those will make sure processRecords is called and the
5321
5411
  // view is resynchronized after
5322
- if (this.delayedFlush >= 0 || this.view.inputState.pendingAndroidKey)
5412
+ if (this.delayedFlush >= 0 || this.delayedAndroidKey)
5323
5413
  return;
5414
+ if (readSelection)
5415
+ this.readSelectionRange();
5324
5416
  let { from, to, typeOver } = this.processRecords();
5325
5417
  let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5326
5418
  if (from < 0 && !newSel)
@@ -5330,7 +5422,7 @@ class DOMObserver {
5330
5422
  this.onChange(from, to, typeOver);
5331
5423
  // The view wasn't updated
5332
5424
  if (this.view.state == startState)
5333
- this.view.docView.reset(newSel);
5425
+ this.view.update([]);
5334
5426
  }
5335
5427
  readMutation(rec) {
5336
5428
  let cView = this.view.docView.nearest(rec.target);
@@ -5549,76 +5641,6 @@ function findDiff(a, b, preferredPos, preferredSide) {
5549
5641
  }
5550
5642
  return { from, toA, toB };
5551
5643
  }
5552
- class DOMReader {
5553
- constructor(points, view) {
5554
- this.points = points;
5555
- this.view = view;
5556
- this.text = "";
5557
- this.lineBreak = view.state.lineBreak;
5558
- }
5559
- readRange(start, end) {
5560
- if (!start)
5561
- return;
5562
- let parent = start.parentNode;
5563
- for (let cur = start;;) {
5564
- this.findPointBefore(parent, cur);
5565
- this.readNode(cur);
5566
- let next = cur.nextSibling;
5567
- if (next == end)
5568
- break;
5569
- let view = ContentView.get(cur), nextView = ContentView.get(next);
5570
- if (view && nextView ? view.breakAfter :
5571
- (view ? view.breakAfter : isBlockElement(cur)) ||
5572
- (isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
5573
- this.text += this.lineBreak;
5574
- cur = next;
5575
- }
5576
- this.findPointBefore(parent, end);
5577
- }
5578
- readNode(node) {
5579
- if (node.cmIgnore)
5580
- return;
5581
- let view = ContentView.get(node);
5582
- let fromView = view && view.overrideDOMText;
5583
- let text;
5584
- if (fromView != null)
5585
- text = fromView.sliceString(0, undefined, this.lineBreak);
5586
- else if (node.nodeType == 3)
5587
- text = node.nodeValue;
5588
- else if (node.nodeName == "BR")
5589
- text = node.nextSibling ? this.lineBreak : "";
5590
- else if (node.nodeType == 1)
5591
- this.readRange(node.firstChild, null);
5592
- if (text != null) {
5593
- this.findPointIn(node, text.length);
5594
- this.text += text;
5595
- // Chrome inserts two newlines when pressing shift-enter at the
5596
- // end of a line. This drops one of those.
5597
- if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
5598
- this.text = this.text.slice(0, -1);
5599
- }
5600
- }
5601
- findPointBefore(node, next) {
5602
- for (let point of this.points)
5603
- if (point.node == node && node.childNodes[point.offset] == next)
5604
- point.pos = this.text.length;
5605
- }
5606
- findPointIn(node, maxLen) {
5607
- for (let point of this.points)
5608
- if (point.node == node)
5609
- point.pos = this.text.length + Math.min(point.offset, maxLen);
5610
- }
5611
- }
5612
- function isBlockElement(node) {
5613
- return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
5614
- }
5615
- class DOMPoint {
5616
- constructor(node, offset) {
5617
- this.node = node;
5618
- this.offset = offset;
5619
- this.pos = -1;
5620
- }
5621
- }
5622
5644
  function selectionPoints(view) {
5623
5645
  let result = [];
5624
5646
  if (view.root.activeElement != view.contentDOM)
@@ -5904,7 +5926,9 @@ class EditorView {
5904
5926
  if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
5905
5927
  break;
5906
5928
  if (i > 5) {
5907
- console.warn(this.measureRequests.length ? "Measure loop restarted more than 5 times" : "Viewport failed to stabilize");
5929
+ console.warn(this.measureRequests.length
5930
+ ? "Measure loop restarted more than 5 times"
5931
+ : "Viewport failed to stabilize");
5908
5932
  break;
5909
5933
  }
5910
5934
  let measuring = [];
@@ -5950,7 +5974,8 @@ class EditorView {
5950
5974
  }
5951
5975
  if (redrawn)
5952
5976
  this.docView.updateSelection(true);
5953
- if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
5977
+ if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
5978
+ this.measureRequests.length == 0)
5954
5979
  break;
5955
5980
  }
5956
5981
  }
@@ -6242,6 +6267,11 @@ class EditorView {
6242
6267
  Find the DOM parent node and offset (child offset if `node` is
6243
6268
  an element, character offset when it is a text node) at the
6244
6269
  given document position.
6270
+
6271
+ Note that for positions that aren't currently in
6272
+ `visibleRanges`, the resulting DOM position isn't necessarily
6273
+ meaningful (it may just point before or after a placeholder
6274
+ element).
6245
6275
  */
6246
6276
  domAtPos(pos) {
6247
6277
  return this.docView.domAtPos(pos);
@@ -6882,7 +6912,7 @@ function measureRange(view, range) {
6882
6912
  let ltr = view.textDirection == exports.Direction.LTR;
6883
6913
  let content = view.contentDOM, contentRect = content.getBoundingClientRect(), base = getBase(view);
6884
6914
  let lineStyle = window.getComputedStyle(content.firstChild);
6885
- let leftSide = contentRect.left + parseInt(lineStyle.paddingLeft);
6915
+ let leftSide = contentRect.left + parseInt(lineStyle.paddingLeft) + Math.min(0, parseInt(lineStyle.textIndent));
6886
6916
  let rightSide = contentRect.right - parseInt(lineStyle.paddingRight);
6887
6917
  let startBlock = blockAt(view, from), endBlock = blockAt(view, to);
6888
6918
  let visualStart = startBlock.type == exports.BlockType.Text ? startBlock : null;
@@ -6902,7 +6932,7 @@ function measureRange(view, range) {
6902
6932
  let between = [];
6903
6933
  if ((visualStart || startBlock).to < (visualEnd || endBlock).from - 1)
6904
6934
  between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
6905
- else if (top.bottom < bottom.top && blockAt(view, (top.bottom + bottom.top) / 2).type == exports.BlockType.Text)
6935
+ else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == exports.BlockType.Text)
6906
6936
  top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
6907
6937
  return pieces(top).concat(between).concat(pieces(bottom));
6908
6938
  }
@@ -6967,6 +6997,98 @@ function measureCursor(view, cursor, primary) {
6967
6997
  return new Piece(pos.left - base.left, pos.top - base.top, -1, pos.bottom - pos.top, primary ? "cm-cursor cm-cursor-primary" : "cm-cursor cm-cursor-secondary");
6968
6998
  }
6969
6999
 
7000
+ const setDropCursorPos = state.StateEffect.define({
7001
+ map(pos, mapping) { return pos == null ? null : mapping.mapPos(pos); }
7002
+ });
7003
+ const dropCursorPos = state.StateField.define({
7004
+ create() { return null; },
7005
+ update(pos, tr) {
7006
+ if (pos != null)
7007
+ pos = tr.changes.mapPos(pos);
7008
+ return tr.effects.reduce((pos, e) => e.is(setDropCursorPos) ? e.value : pos, pos);
7009
+ }
7010
+ });
7011
+ const drawDropCursor = ViewPlugin.fromClass(class {
7012
+ constructor(view) {
7013
+ this.view = view;
7014
+ this.cursor = null;
7015
+ this.measureReq = { read: this.readPos.bind(this), write: this.drawCursor.bind(this) };
7016
+ }
7017
+ update(update) {
7018
+ var _a;
7019
+ let cursorPos = update.state.field(dropCursorPos);
7020
+ if (cursorPos == null) {
7021
+ if (this.cursor != null) {
7022
+ (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.remove();
7023
+ this.cursor = null;
7024
+ }
7025
+ }
7026
+ else {
7027
+ if (!this.cursor) {
7028
+ this.cursor = this.view.scrollDOM.appendChild(document.createElement("div"));
7029
+ this.cursor.className = "cm-dropCursor";
7030
+ }
7031
+ if (update.startState.field(dropCursorPos) != cursorPos || update.docChanged || update.geometryChanged)
7032
+ this.view.requestMeasure(this.measureReq);
7033
+ }
7034
+ }
7035
+ readPos() {
7036
+ let pos = this.view.state.field(dropCursorPos);
7037
+ let rect = pos != null && this.view.coordsAtPos(pos);
7038
+ if (!rect)
7039
+ return null;
7040
+ let outer = this.view.scrollDOM.getBoundingClientRect();
7041
+ return {
7042
+ left: rect.left - outer.left + this.view.scrollDOM.scrollLeft,
7043
+ top: rect.top - outer.top + this.view.scrollDOM.scrollTop,
7044
+ height: rect.bottom - rect.top
7045
+ };
7046
+ }
7047
+ drawCursor(pos) {
7048
+ if (this.cursor) {
7049
+ if (pos) {
7050
+ this.cursor.style.left = pos.left + "px";
7051
+ this.cursor.style.top = pos.top + "px";
7052
+ this.cursor.style.height = pos.height + "px";
7053
+ }
7054
+ else {
7055
+ this.cursor.style.left = "-100000px";
7056
+ }
7057
+ }
7058
+ }
7059
+ destroy() {
7060
+ if (this.cursor)
7061
+ this.cursor.remove();
7062
+ }
7063
+ setDropPos(pos) {
7064
+ if (this.view.state.field(dropCursorPos) != pos)
7065
+ this.view.dispatch({ effects: setDropCursorPos.of(pos) });
7066
+ }
7067
+ }, {
7068
+ eventHandlers: {
7069
+ dragover(event) {
7070
+ this.setDropPos(this.view.posAtCoords({ x: event.clientX, y: event.clientY }));
7071
+ },
7072
+ dragleave(event) {
7073
+ if (event.target == this.view.contentDOM || !this.view.contentDOM.contains(event.relatedTarget))
7074
+ this.setDropPos(null);
7075
+ },
7076
+ dragend() {
7077
+ this.setDropPos(null);
7078
+ },
7079
+ drop() {
7080
+ this.setDropPos(null);
7081
+ }
7082
+ }
7083
+ });
7084
+ /**
7085
+ Draws a cursor at the current drop position when something is
7086
+ dragged over the editor.
7087
+ */
7088
+ function dropCursor() {
7089
+ return [dropCursorPos, drawDropCursor];
7090
+ }
7091
+
6970
7092
  function iterMatches(doc, re, from, to, f) {
6971
7093
  re.lastIndex = 0;
6972
7094
  for (let cursor = doc.iterRange(from, to), pos = from, m; !cursor.next().done; pos += cursor.value.length) {
@@ -6975,6 +7097,22 @@ function iterMatches(doc, re, from, to, f) {
6975
7097
  f(pos + m.index, pos + m.index + m[0].length, m);
6976
7098
  }
6977
7099
  }
7100
+ function matchRanges(view, maxLength) {
7101
+ let visible = view.visibleRanges;
7102
+ if (visible.length == 1 && visible[0].from == view.viewport.from &&
7103
+ visible[0].to == view.viewport.to)
7104
+ return visible;
7105
+ let result = [];
7106
+ for (let { from, to } of visible) {
7107
+ from = Math.max(view.state.doc.lineAt(from).from, from - maxLength);
7108
+ to = Math.min(view.state.doc.lineAt(to).to, to + maxLength);
7109
+ if (result.length && result[result.length - 1].to >= from)
7110
+ result[result.length - 1].to = to;
7111
+ else
7112
+ result.push({ from, to });
7113
+ }
7114
+ return result;
7115
+ }
6978
7116
  /**
6979
7117
  Helper class used to make it easier to maintain decorations on
6980
7118
  visible code that matches a given regular expression. To be used
@@ -6986,12 +7124,13 @@ class MatchDecorator {
6986
7124
  Create a decorator.
6987
7125
  */
6988
7126
  constructor(config) {
6989
- let { regexp, decoration, boundary } = config;
7127
+ let { regexp, decoration, boundary, maxLength = 1000 } = config;
6990
7128
  if (!regexp.global)
6991
7129
  throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
6992
7130
  this.regexp = regexp;
6993
7131
  this.getDeco = typeof decoration == "function" ? decoration : () => decoration;
6994
7132
  this.boundary = boundary;
7133
+ this.maxLength = maxLength;
6995
7134
  }
6996
7135
  /**
6997
7136
  Compute the full set of decorations for matches in the given
@@ -7000,7 +7139,7 @@ class MatchDecorator {
7000
7139
  */
7001
7140
  createDeco(view) {
7002
7141
  let build = new rangeset.RangeSetBuilder();
7003
- for (let { from, to } of view.visibleRanges)
7142
+ for (let { from, to } of matchRanges(view, this.maxLength))
7004
7143
  iterMatches(view.state.doc, this.regexp, from, to, (a, b, m) => build.add(a, b, this.getDeco(m, view, a)));
7005
7144
  return build.finish();
7006
7145
  }
@@ -7317,6 +7456,7 @@ exports.ViewUpdate = ViewUpdate;
7317
7456
  exports.WidgetType = WidgetType;
7318
7457
  exports.__test = __test;
7319
7458
  exports.drawSelection = drawSelection;
7459
+ exports.dropCursor = dropCursor;
7320
7460
  exports.highlightActiveLine = highlightActiveLine;
7321
7461
  exports.highlightSpecialChars = highlightSpecialChars;
7322
7462
  exports.keymap = keymap;