@codemirror/view 0.19.29 → 0.19.30

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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 0.19.30 (2021-12-13)
2
+
3
+ ### Bug fixes
4
+
5
+ Refine Android key event handling to work properly in a GBoard corner case where pressing Enter fires a bunch of spurious deleteContentBackward events.
6
+
7
+ Fix a crash in `drawSelection` for some kinds of selections.
8
+
9
+ Prevent a possibility where some content updates causes duplicate text to remain in DOM.
10
+
11
+ ### New features
12
+
13
+ Support a `maxLength` option to `MatchDecorator` that allows user code to control how far it scans into hidden parts of viewport lines.
14
+
1
15
  ## 0.19.29 (2021-12-09)
2
16
 
3
17
  ### Bug fixes
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) {
@@ -2398,16 +2399,6 @@ class DocView extends ContentView {
2398
2399
  return true;
2399
2400
  }
2400
2401
  }
2401
- reset(sel) {
2402
- if (this.dirty) {
2403
- this.view.observer.ignore(() => this.view.docView.sync());
2404
- this.dirty = 0 /* Not */;
2405
- this.updateSelection(true);
2406
- }
2407
- else {
2408
- this.updateSelection();
2409
- }
2410
- }
2411
2402
  // Used by update and the constructor do perform the actual DOM
2412
2403
  // update
2413
2404
  updateInner(changes, deco, oldLength) {
@@ -2483,7 +2474,8 @@ class DocView extends ContentView {
2483
2474
  // inside an uneditable node, and not bring it back when we
2484
2475
  // move the cursor to its proper position. This tries to
2485
2476
  // restore the keyboard by cycling focus.
2486
- 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)) {
2487
2479
  this.dom.blur();
2488
2480
  this.dom.focus({ preventScroll: true });
2489
2481
  }
@@ -3136,14 +3128,6 @@ class InputState {
3136
3128
  constructor(view) {
3137
3129
  this.lastKeyCode = 0;
3138
3130
  this.lastKeyTime = 0;
3139
- // On Chrome Android, backspace near widgets is just completely
3140
- // broken, and there are no key events, so we need to handle the
3141
- // beforeinput event. Deleting stuff will often create a flurry of
3142
- // events, and interrupting it before it is done just makes
3143
- // subsequent events even more broken, so again, we hold off doing
3144
- // anything until the browser is finished with whatever it is trying
3145
- // to do.
3146
- this.pendingAndroidKey = undefined;
3147
3131
  // On iOS, some keys need to have their default behavior happen
3148
3132
  // (after which we retroactively handle them and reset the DOM) to
3149
3133
  // avoid messing up the virtual keyboard state.
@@ -3212,22 +3196,15 @@ class InputState {
3212
3196
  }
3213
3197
  runCustomHandlers(type, view, event) {
3214
3198
  for (let set of this.customHandlers) {
3215
- let handler = set.handlers[type], handled = false;
3199
+ let handler = set.handlers[type];
3216
3200
  if (handler) {
3217
3201
  try {
3218
- handled = handler.call(set.plugin, event, view);
3202
+ if (handler.call(set.plugin, event, view))
3203
+ return true;
3219
3204
  }
3220
3205
  catch (e) {
3221
3206
  logException(view.state, e);
3222
3207
  }
3223
- if (handled || event.defaultPrevented) {
3224
- // Chrome for Android often applies a bunch of nonsensical
3225
- // DOM changes after an enter press, even when
3226
- // preventDefault-ed. This tries to ignore those.
3227
- if (browser.android && type == "keydown" && event.keyCode == 13)
3228
- view.observer.flushSoon();
3229
- return true;
3230
- }
3231
3208
  }
3232
3209
  }
3233
3210
  return false;
@@ -3251,6 +3228,16 @@ class InputState {
3251
3228
  this.lastKeyTime = Date.now();
3252
3229
  if (this.screenKeyEvent(view, event))
3253
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
+ }
3254
3241
  // Prevent the default behavior of Enter on iOS makes the
3255
3242
  // virtual keyboard get stuck in the wrong (lowercase)
3256
3243
  // state. So we let it go through, and then, in
@@ -3272,24 +3259,6 @@ class InputState {
3272
3259
  this.pendingIOSKey = undefined;
3273
3260
  return dispatchKey(view.contentDOM, key.key, key.keyCode);
3274
3261
  }
3275
- // This causes the DOM observer to pause for a bit, and sets an
3276
- // animation frame (which seems the most reliable way to detect
3277
- // 'Chrome is done flailing about messing with the DOM') to fire a
3278
- // fake key event and re-sync the view again.
3279
- setPendingAndroidKey(view, pending) {
3280
- this.pendingAndroidKey = pending;
3281
- requestAnimationFrame(() => {
3282
- let key = this.pendingAndroidKey;
3283
- if (!key)
3284
- return;
3285
- this.pendingAndroidKey = undefined;
3286
- view.observer.processRecords();
3287
- let startState = view.state;
3288
- dispatchKey(view.contentDOM, key.key, key.keyCode);
3289
- if (view.state == startState)
3290
- view.docView.reset(true);
3291
- });
3292
- }
3293
3262
  ignoreDuringComposition(event) {
3294
3263
  if (!/^key/.test(event.type))
3295
3264
  return false;
@@ -3768,12 +3737,12 @@ handlers.compositionstart = handlers.compositionupdate = view => {
3768
3737
  if (view.inputState.compositionFirstChange == null)
3769
3738
  view.inputState.compositionFirstChange = true;
3770
3739
  if (view.inputState.composing < 0) {
3740
+ // FIXME possibly set a timeout to clear it again on Android
3741
+ view.inputState.composing = 0;
3771
3742
  if (view.docView.compositionDeco.size) {
3772
3743
  view.observer.flush();
3773
3744
  forceClearComposition(view, true);
3774
3745
  }
3775
- // FIXME possibly set a timeout to clear it again on Android
3776
- view.inputState.composing = 0;
3777
3746
  }
3778
3747
  };
3779
3748
  handlers.compositionend = view => {
@@ -3799,7 +3768,7 @@ handlers.beforeinput = (view, event) => {
3799
3768
  // seems to do nothing at all on Chrome).
3800
3769
  let pending;
3801
3770
  if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
3802
- view.inputState.setPendingAndroidKey(view, pending);
3771
+ view.observer.delayAndroidKey(pending.key, pending.keyCode);
3803
3772
  if (pending.key == "Backspace" || pending.key == "Delete") {
3804
3773
  let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
3805
3774
  setTimeout(() => {
@@ -5188,6 +5157,7 @@ class DOMObserver {
5188
5157
  this.delayedFlush = -1;
5189
5158
  this.resizeTimeout = -1;
5190
5159
  this.queue = [];
5160
+ this.delayedAndroidKey = null;
5191
5161
  this.scrollTargets = [];
5192
5162
  this.intersection = null;
5193
5163
  this.resize = null;
@@ -5271,7 +5241,7 @@ class DOMObserver {
5271
5241
  }
5272
5242
  }
5273
5243
  onSelectionChange(event) {
5274
- if (!this.readSelectionRange())
5244
+ if (!this.readSelectionRange() || this.delayedAndroidKey)
5275
5245
  return;
5276
5246
  let { view } = this, sel = this.selectionRange;
5277
5247
  if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
@@ -5367,6 +5337,32 @@ class DOMObserver {
5367
5337
  this.queue.length = 0;
5368
5338
  this.selectionChanged = false;
5369
5339
  }
5340
+ // Chrome Android, especially in combination with GBoard, not only
5341
+ // doesn't reliably fire regular key events, but also often
5342
+ // surrounds the effect of enter or backspace with a bunch of
5343
+ // composition events that, when interrupted, cause text duplication
5344
+ // or other kinds of corruption. This hack makes the editor back off
5345
+ // from handling DOM changes for a moment when such a key is
5346
+ // detected (via beforeinput or keydown), and then dispatches the
5347
+ // key event, throwing away the DOM changes if it gets handled.
5348
+ delayAndroidKey(key, keyCode) {
5349
+ if (!this.delayedAndroidKey)
5350
+ requestAnimationFrame(() => {
5351
+ let key = this.delayedAndroidKey;
5352
+ this.delayedAndroidKey = null;
5353
+ let startState = this.view.state;
5354
+ if (dispatchKey(this.view.contentDOM, key.key, key.keyCode))
5355
+ this.processRecords();
5356
+ else
5357
+ this.flush();
5358
+ if (this.view.state == startState)
5359
+ this.view.update([]);
5360
+ });
5361
+ // Since backspace beforeinput is sometimes signalled spuriously,
5362
+ // Enter always takes precedence.
5363
+ if (!this.delayedAndroidKey || key == "Enter")
5364
+ this.delayedAndroidKey = { key, keyCode };
5365
+ }
5370
5366
  flushSoon() {
5371
5367
  if (this.delayedFlush < 0)
5372
5368
  this.delayedFlush = window.setTimeout(() => { this.delayedFlush = -1; this.flush(); }, 20);
@@ -5403,13 +5399,13 @@ class DOMObserver {
5403
5399
  }
5404
5400
  // Apply pending changes, if any
5405
5401
  flush(readSelection = true) {
5406
- if (readSelection)
5407
- this.readSelectionRange();
5408
5402
  // Completely hold off flushing when pending keys are set—the code
5409
5403
  // managing those will make sure processRecords is called and the
5410
5404
  // view is resynchronized after
5411
- if (this.delayedFlush >= 0 || this.view.inputState.pendingAndroidKey)
5405
+ if (this.delayedFlush >= 0 || this.delayedAndroidKey)
5412
5406
  return;
5407
+ if (readSelection)
5408
+ this.readSelectionRange();
5413
5409
  let { from, to, typeOver } = this.processRecords();
5414
5410
  let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5415
5411
  if (from < 0 && !newSel)
@@ -6929,7 +6925,7 @@ function measureRange(view, range) {
6929
6925
  let between = [];
6930
6926
  if ((visualStart || startBlock).to < (visualEnd || endBlock).from - 1)
6931
6927
  between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
6932
- else if (top.bottom < bottom.top && blockAt(view, (top.bottom + bottom.top) / 2).type == exports.BlockType.Text)
6928
+ else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == exports.BlockType.Text)
6933
6929
  top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
6934
6930
  return pieces(top).concat(between).concat(pieces(bottom));
6935
6931
  }
@@ -7002,6 +6998,22 @@ function iterMatches(doc, re, from, to, f) {
7002
6998
  f(pos + m.index, pos + m.index + m[0].length, m);
7003
6999
  }
7004
7000
  }
7001
+ function matchRanges(view, maxLength) {
7002
+ let visible = view.visibleRanges;
7003
+ if (visible.length == 1 && visible[0].from == view.viewport.from &&
7004
+ visible[0].to == view.viewport.to)
7005
+ return visible;
7006
+ let result = [];
7007
+ for (let { from, to } of visible) {
7008
+ from = Math.max(view.state.doc.lineAt(from).from, from - maxLength);
7009
+ to = Math.min(view.state.doc.lineAt(to).to, to + maxLength);
7010
+ if (result.length && result[result.length - 1].to >= from)
7011
+ result[result.length - 1].to = to;
7012
+ else
7013
+ result.push({ from, to });
7014
+ }
7015
+ return result;
7016
+ }
7005
7017
  /**
7006
7018
  Helper class used to make it easier to maintain decorations on
7007
7019
  visible code that matches a given regular expression. To be used
@@ -7013,12 +7025,13 @@ class MatchDecorator {
7013
7025
  Create a decorator.
7014
7026
  */
7015
7027
  constructor(config) {
7016
- let { regexp, decoration, boundary } = config;
7028
+ let { regexp, decoration, boundary, maxLength = 1000 } = config;
7017
7029
  if (!regexp.global)
7018
7030
  throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
7019
7031
  this.regexp = regexp;
7020
7032
  this.getDeco = typeof decoration == "function" ? decoration : () => decoration;
7021
7033
  this.boundary = boundary;
7034
+ this.maxLength = maxLength;
7022
7035
  }
7023
7036
  /**
7024
7037
  Compute the full set of decorations for matches in the given
@@ -7027,7 +7040,7 @@ class MatchDecorator {
7027
7040
  */
7028
7041
  createDeco(view) {
7029
7042
  let build = new rangeset.RangeSetBuilder();
7030
- for (let { from, to } of view.visibleRanges)
7043
+ for (let { from, to } of matchRanges(view, this.maxLength))
7031
7044
  iterMatches(view.state.doc, this.regexp, from, to, (a, b, m) => build.add(a, b, this.getDeco(m, view, a)));
7032
7045
  return build.finish();
7033
7046
  }
package/dist/index.d.ts CHANGED
@@ -1362,6 +1362,7 @@ declare class MatchDecorator {
1362
1362
  private regexp;
1363
1363
  private getDeco;
1364
1364
  private boundary;
1365
+ private maxLength;
1365
1366
  /**
1366
1367
  Create a decorator.
1367
1368
  */
@@ -1384,6 +1385,14 @@ declare class MatchDecorator {
1384
1385
  the amount of re-matching.
1385
1386
  */
1386
1387
  boundary?: RegExp;
1388
+ /**
1389
+ Matching happens by line, by default, but when lines are
1390
+ folded or very long lines are only partially drawn, the
1391
+ decorator may avoid matching part of them for speed. This
1392
+ controls how much additional invisible content it should
1393
+ include in its matches. Defaults to 1000.
1394
+ */
1395
+ maxLength?: number;
1387
1396
  });
1388
1397
  /**
1389
1398
  Compute the full set of decorations for matches in the given
package/dist/index.js CHANGED
@@ -655,6 +655,7 @@ class TextView extends ContentView {
655
655
  split(from) {
656
656
  let result = new TextView(this.text.slice(from));
657
657
  this.text = this.text.slice(0, from);
658
+ this.markDirty();
658
659
  return result;
659
660
  }
660
661
  localPosFromDOM(node, offset) {
@@ -2393,16 +2394,6 @@ class DocView extends ContentView {
2393
2394
  return true;
2394
2395
  }
2395
2396
  }
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();
2404
- }
2405
- }
2406
2397
  // Used by update and the constructor do perform the actual DOM
2407
2398
  // update
2408
2399
  updateInner(changes, deco, oldLength) {
@@ -2478,7 +2469,8 @@ class DocView extends ContentView {
2478
2469
  // inside an uneditable node, and not bring it back when we
2479
2470
  // move the cursor to its proper position. This tries to
2480
2471
  // restore the keyboard by cycling focus.
2481
- if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) && inUneditable(domSel.focusNode, this.dom)) {
2472
+ if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) &&
2473
+ inUneditable(domSel.focusNode, this.dom)) {
2482
2474
  this.dom.blur();
2483
2475
  this.dom.focus({ preventScroll: true });
2484
2476
  }
@@ -3131,14 +3123,6 @@ class InputState {
3131
3123
  constructor(view) {
3132
3124
  this.lastKeyCode = 0;
3133
3125
  this.lastKeyTime = 0;
3134
- // On Chrome Android, backspace near widgets is just completely
3135
- // broken, and there are no key events, so we need to handle the
3136
- // beforeinput event. Deleting stuff will often create a flurry of
3137
- // events, and interrupting it before it is done just makes
3138
- // subsequent events even more broken, so again, we hold off doing
3139
- // anything until the browser is finished with whatever it is trying
3140
- // to do.
3141
- this.pendingAndroidKey = undefined;
3142
3126
  // On iOS, some keys need to have their default behavior happen
3143
3127
  // (after which we retroactively handle them and reset the DOM) to
3144
3128
  // avoid messing up the virtual keyboard state.
@@ -3207,22 +3191,15 @@ class InputState {
3207
3191
  }
3208
3192
  runCustomHandlers(type, view, event) {
3209
3193
  for (let set of this.customHandlers) {
3210
- let handler = set.handlers[type], handled = false;
3194
+ let handler = set.handlers[type];
3211
3195
  if (handler) {
3212
3196
  try {
3213
- handled = handler.call(set.plugin, event, view);
3197
+ if (handler.call(set.plugin, event, view))
3198
+ return true;
3214
3199
  }
3215
3200
  catch (e) {
3216
3201
  logException(view.state, e);
3217
3202
  }
3218
- if (handled || event.defaultPrevented) {
3219
- // Chrome for Android often applies a bunch of nonsensical
3220
- // DOM changes after an enter press, even when
3221
- // preventDefault-ed. This tries to ignore those.
3222
- if (browser.android && type == "keydown" && event.keyCode == 13)
3223
- view.observer.flushSoon();
3224
- return true;
3225
- }
3226
3203
  }
3227
3204
  }
3228
3205
  return false;
@@ -3246,6 +3223,16 @@ class InputState {
3246
3223
  this.lastKeyTime = Date.now();
3247
3224
  if (this.screenKeyEvent(view, event))
3248
3225
  return true;
3226
+ // Chrome for Android usually doesn't fire proper key events, but
3227
+ // occasionally does, usually surrounded by a bunch of complicated
3228
+ // composition changes. When an enter or backspace key event is
3229
+ // seen, hold off on handling DOM events for a bit, and then
3230
+ // dispatch it.
3231
+ if (browser.android && browser.chrome && !event.synthetic &&
3232
+ (event.keyCode == 13 || event.keyCode == 8)) {
3233
+ view.observer.delayAndroidKey(event.key, event.keyCode);
3234
+ return true;
3235
+ }
3249
3236
  // Prevent the default behavior of Enter on iOS makes the
3250
3237
  // virtual keyboard get stuck in the wrong (lowercase)
3251
3238
  // state. So we let it go through, and then, in
@@ -3267,24 +3254,6 @@ class InputState {
3267
3254
  this.pendingIOSKey = undefined;
3268
3255
  return dispatchKey(view.contentDOM, key.key, key.keyCode);
3269
3256
  }
3270
- // This causes the DOM observer to pause for a bit, and sets an
3271
- // animation frame (which seems the most reliable way to detect
3272
- // 'Chrome is done flailing about messing with the DOM') to fire a
3273
- // fake key event and re-sync the view again.
3274
- setPendingAndroidKey(view, pending) {
3275
- this.pendingAndroidKey = pending;
3276
- requestAnimationFrame(() => {
3277
- let key = this.pendingAndroidKey;
3278
- if (!key)
3279
- return;
3280
- this.pendingAndroidKey = undefined;
3281
- view.observer.processRecords();
3282
- let startState = view.state;
3283
- dispatchKey(view.contentDOM, key.key, key.keyCode);
3284
- if (view.state == startState)
3285
- view.docView.reset(true);
3286
- });
3287
- }
3288
3257
  ignoreDuringComposition(event) {
3289
3258
  if (!/^key/.test(event.type))
3290
3259
  return false;
@@ -3763,12 +3732,12 @@ handlers.compositionstart = handlers.compositionupdate = view => {
3763
3732
  if (view.inputState.compositionFirstChange == null)
3764
3733
  view.inputState.compositionFirstChange = true;
3765
3734
  if (view.inputState.composing < 0) {
3735
+ // FIXME possibly set a timeout to clear it again on Android
3736
+ view.inputState.composing = 0;
3766
3737
  if (view.docView.compositionDeco.size) {
3767
3738
  view.observer.flush();
3768
3739
  forceClearComposition(view, true);
3769
3740
  }
3770
- // FIXME possibly set a timeout to clear it again on Android
3771
- view.inputState.composing = 0;
3772
3741
  }
3773
3742
  };
3774
3743
  handlers.compositionend = view => {
@@ -3794,7 +3763,7 @@ handlers.beforeinput = (view, event) => {
3794
3763
  // seems to do nothing at all on Chrome).
3795
3764
  let pending;
3796
3765
  if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
3797
- view.inputState.setPendingAndroidKey(view, pending);
3766
+ view.observer.delayAndroidKey(pending.key, pending.keyCode);
3798
3767
  if (pending.key == "Backspace" || pending.key == "Delete") {
3799
3768
  let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
3800
3769
  setTimeout(() => {
@@ -5182,6 +5151,7 @@ class DOMObserver {
5182
5151
  this.delayedFlush = -1;
5183
5152
  this.resizeTimeout = -1;
5184
5153
  this.queue = [];
5154
+ this.delayedAndroidKey = null;
5185
5155
  this.scrollTargets = [];
5186
5156
  this.intersection = null;
5187
5157
  this.resize = null;
@@ -5265,7 +5235,7 @@ class DOMObserver {
5265
5235
  }
5266
5236
  }
5267
5237
  onSelectionChange(event) {
5268
- if (!this.readSelectionRange())
5238
+ if (!this.readSelectionRange() || this.delayedAndroidKey)
5269
5239
  return;
5270
5240
  let { view } = this, sel = this.selectionRange;
5271
5241
  if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
@@ -5361,6 +5331,32 @@ class DOMObserver {
5361
5331
  this.queue.length = 0;
5362
5332
  this.selectionChanged = false;
5363
5333
  }
5334
+ // Chrome Android, especially in combination with GBoard, not only
5335
+ // doesn't reliably fire regular key events, but also often
5336
+ // surrounds the effect of enter or backspace with a bunch of
5337
+ // composition events that, when interrupted, cause text duplication
5338
+ // or other kinds of corruption. This hack makes the editor back off
5339
+ // from handling DOM changes for a moment when such a key is
5340
+ // detected (via beforeinput or keydown), and then dispatches the
5341
+ // key event, throwing away the DOM changes if it gets handled.
5342
+ delayAndroidKey(key, keyCode) {
5343
+ if (!this.delayedAndroidKey)
5344
+ requestAnimationFrame(() => {
5345
+ let key = this.delayedAndroidKey;
5346
+ this.delayedAndroidKey = null;
5347
+ let startState = this.view.state;
5348
+ if (dispatchKey(this.view.contentDOM, key.key, key.keyCode))
5349
+ this.processRecords();
5350
+ else
5351
+ this.flush();
5352
+ if (this.view.state == startState)
5353
+ this.view.update([]);
5354
+ });
5355
+ // Since backspace beforeinput is sometimes signalled spuriously,
5356
+ // Enter always takes precedence.
5357
+ if (!this.delayedAndroidKey || key == "Enter")
5358
+ this.delayedAndroidKey = { key, keyCode };
5359
+ }
5364
5360
  flushSoon() {
5365
5361
  if (this.delayedFlush < 0)
5366
5362
  this.delayedFlush = window.setTimeout(() => { this.delayedFlush = -1; this.flush(); }, 20);
@@ -5397,13 +5393,13 @@ class DOMObserver {
5397
5393
  }
5398
5394
  // Apply pending changes, if any
5399
5395
  flush(readSelection = true) {
5400
- if (readSelection)
5401
- this.readSelectionRange();
5402
5396
  // Completely hold off flushing when pending keys are set—the code
5403
5397
  // managing those will make sure processRecords is called and the
5404
5398
  // view is resynchronized after
5405
- if (this.delayedFlush >= 0 || this.view.inputState.pendingAndroidKey)
5399
+ if (this.delayedFlush >= 0 || this.delayedAndroidKey)
5406
5400
  return;
5401
+ if (readSelection)
5402
+ this.readSelectionRange();
5407
5403
  let { from, to, typeOver } = this.processRecords();
5408
5404
  let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5409
5405
  if (from < 0 && !newSel)
@@ -6923,7 +6919,7 @@ function measureRange(view, range) {
6923
6919
  let between = [];
6924
6920
  if ((visualStart || startBlock).to < (visualEnd || endBlock).from - 1)
6925
6921
  between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
6926
- else if (top.bottom < bottom.top && blockAt(view, (top.bottom + bottom.top) / 2).type == BlockType.Text)
6922
+ else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == BlockType.Text)
6927
6923
  top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
6928
6924
  return pieces(top).concat(between).concat(pieces(bottom));
6929
6925
  }
@@ -6996,6 +6992,22 @@ function iterMatches(doc, re, from, to, f) {
6996
6992
  f(pos + m.index, pos + m.index + m[0].length, m);
6997
6993
  }
6998
6994
  }
6995
+ function matchRanges(view, maxLength) {
6996
+ let visible = view.visibleRanges;
6997
+ if (visible.length == 1 && visible[0].from == view.viewport.from &&
6998
+ visible[0].to == view.viewport.to)
6999
+ return visible;
7000
+ let result = [];
7001
+ for (let { from, to } of visible) {
7002
+ from = Math.max(view.state.doc.lineAt(from).from, from - maxLength);
7003
+ to = Math.min(view.state.doc.lineAt(to).to, to + maxLength);
7004
+ if (result.length && result[result.length - 1].to >= from)
7005
+ result[result.length - 1].to = to;
7006
+ else
7007
+ result.push({ from, to });
7008
+ }
7009
+ return result;
7010
+ }
6999
7011
  /**
7000
7012
  Helper class used to make it easier to maintain decorations on
7001
7013
  visible code that matches a given regular expression. To be used
@@ -7007,12 +7019,13 @@ class MatchDecorator {
7007
7019
  Create a decorator.
7008
7020
  */
7009
7021
  constructor(config) {
7010
- let { regexp, decoration, boundary } = config;
7022
+ let { regexp, decoration, boundary, maxLength = 1000 } = config;
7011
7023
  if (!regexp.global)
7012
7024
  throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
7013
7025
  this.regexp = regexp;
7014
7026
  this.getDeco = typeof decoration == "function" ? decoration : () => decoration;
7015
7027
  this.boundary = boundary;
7028
+ this.maxLength = maxLength;
7016
7029
  }
7017
7030
  /**
7018
7031
  Compute the full set of decorations for matches in the given
@@ -7021,7 +7034,7 @@ class MatchDecorator {
7021
7034
  */
7022
7035
  createDeco(view) {
7023
7036
  let build = new RangeSetBuilder();
7024
- for (let { from, to } of view.visibleRanges)
7037
+ for (let { from, to } of matchRanges(view, this.maxLength))
7025
7038
  iterMatches(view.state.doc, this.regexp, from, to, (a, b, m) => build.add(a, b, this.getDeco(m, view, a)));
7026
7039
  return build.finish();
7027
7040
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemirror/view",
3
- "version": "0.19.29",
3
+ "version": "0.19.30",
4
4
  "description": "DOM view component for the CodeMirror code editor",
5
5
  "scripts": {
6
6
  "test": "cm-runtests",