@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/CHANGELOG.md +46 -0
- package/dist/index.cjs +302 -162
- package/dist/index.d.ts +21 -1
- package/dist/index.js +303 -164
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MapMode, Text as Text$1, Facet, StateEffect, ChangeSet, EditorSelection, CharCategory, EditorState, Transaction, Prec, combineConfig } from '@codemirror/state';
|
|
1
|
+
import { MapMode, Text as Text$1, Facet, StateEffect, ChangeSet, EditorSelection, CharCategory, EditorState, Transaction, Prec, combineConfig, StateField } from '@codemirror/state';
|
|
2
2
|
import { StyleModule } from 'style-mod';
|
|
3
3
|
import { RangeSet, RangeValue, RangeSetBuilder } from '@codemirror/rangeset';
|
|
4
4
|
export { Range } from '@codemirror/rangeset';
|
|
@@ -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) {
|
|
@@ -2249,6 +2250,78 @@ function moveVisually(line, order, dir, start, forward) {
|
|
|
2249
2250
|
return EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
|
|
2250
2251
|
}
|
|
2251
2252
|
|
|
2253
|
+
class DOMReader {
|
|
2254
|
+
constructor(points, view) {
|
|
2255
|
+
this.points = points;
|
|
2256
|
+
this.view = view;
|
|
2257
|
+
this.text = "";
|
|
2258
|
+
this.lineBreak = view.state.lineBreak;
|
|
2259
|
+
}
|
|
2260
|
+
readRange(start, end) {
|
|
2261
|
+
if (!start)
|
|
2262
|
+
return this;
|
|
2263
|
+
let parent = start.parentNode;
|
|
2264
|
+
for (let cur = start;;) {
|
|
2265
|
+
this.findPointBefore(parent, cur);
|
|
2266
|
+
this.readNode(cur);
|
|
2267
|
+
let next = cur.nextSibling;
|
|
2268
|
+
if (next == end)
|
|
2269
|
+
break;
|
|
2270
|
+
let view = ContentView.get(cur), nextView = ContentView.get(next);
|
|
2271
|
+
if (view && nextView ? view.breakAfter :
|
|
2272
|
+
(view ? view.breakAfter : isBlockElement(cur)) ||
|
|
2273
|
+
(isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
|
|
2274
|
+
this.text += this.lineBreak;
|
|
2275
|
+
cur = next;
|
|
2276
|
+
}
|
|
2277
|
+
this.findPointBefore(parent, end);
|
|
2278
|
+
return this;
|
|
2279
|
+
}
|
|
2280
|
+
readNode(node) {
|
|
2281
|
+
if (node.cmIgnore)
|
|
2282
|
+
return;
|
|
2283
|
+
let view = ContentView.get(node);
|
|
2284
|
+
let fromView = view && view.overrideDOMText;
|
|
2285
|
+
let text;
|
|
2286
|
+
if (fromView != null)
|
|
2287
|
+
text = fromView.sliceString(0, undefined, this.lineBreak);
|
|
2288
|
+
else if (node.nodeType == 3)
|
|
2289
|
+
text = node.nodeValue;
|
|
2290
|
+
else if (node.nodeName == "BR")
|
|
2291
|
+
text = node.nextSibling ? this.lineBreak : "";
|
|
2292
|
+
else if (node.nodeType == 1)
|
|
2293
|
+
this.readRange(node.firstChild, null);
|
|
2294
|
+
if (text != null) {
|
|
2295
|
+
this.findPointIn(node, text.length);
|
|
2296
|
+
this.text += text;
|
|
2297
|
+
// Chrome inserts two newlines when pressing shift-enter at the
|
|
2298
|
+
// end of a line. This drops one of those.
|
|
2299
|
+
if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
|
|
2300
|
+
this.text = this.text.slice(0, -1);
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
findPointBefore(node, next) {
|
|
2304
|
+
for (let point of this.points)
|
|
2305
|
+
if (point.node == node && node.childNodes[point.offset] == next)
|
|
2306
|
+
point.pos = this.text.length;
|
|
2307
|
+
}
|
|
2308
|
+
findPointIn(node, maxLen) {
|
|
2309
|
+
for (let point of this.points)
|
|
2310
|
+
if (point.node == node)
|
|
2311
|
+
point.pos = this.text.length + Math.min(point.offset, maxLen);
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
function isBlockElement(node) {
|
|
2315
|
+
return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
|
|
2316
|
+
}
|
|
2317
|
+
class DOMPoint {
|
|
2318
|
+
constructor(node, offset) {
|
|
2319
|
+
this.node = node;
|
|
2320
|
+
this.offset = offset;
|
|
2321
|
+
this.pos = -1;
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2252
2325
|
class DocView extends ContentView {
|
|
2253
2326
|
constructor(view) {
|
|
2254
2327
|
super();
|
|
@@ -2298,7 +2371,7 @@ class DocView extends ContentView {
|
|
|
2298
2371
|
}
|
|
2299
2372
|
if (this.view.inputState.composing < 0)
|
|
2300
2373
|
this.compositionDeco = Decoration.none;
|
|
2301
|
-
else if (update.transactions.length)
|
|
2374
|
+
else if (update.transactions.length || this.dirty)
|
|
2302
2375
|
this.compositionDeco = computeCompositionDeco(this.view, update.changes);
|
|
2303
2376
|
// When the DOM nodes around the selection are moved to another
|
|
2304
2377
|
// parent, Chrome sometimes reports a different selection through
|
|
@@ -2321,16 +2394,6 @@ class DocView extends ContentView {
|
|
|
2321
2394
|
return true;
|
|
2322
2395
|
}
|
|
2323
2396
|
}
|
|
2324
|
-
reset(sel) {
|
|
2325
|
-
if (this.dirty) {
|
|
2326
|
-
this.view.observer.ignore(() => this.view.docView.sync());
|
|
2327
|
-
this.dirty = 0 /* Not */;
|
|
2328
|
-
this.updateSelection(true);
|
|
2329
|
-
}
|
|
2330
|
-
else {
|
|
2331
|
-
this.updateSelection();
|
|
2332
|
-
}
|
|
2333
|
-
}
|
|
2334
2397
|
// Used by update and the constructor do perform the actual DOM
|
|
2335
2398
|
// update
|
|
2336
2399
|
updateInner(changes, deco, oldLength) {
|
|
@@ -2406,7 +2469,8 @@ class DocView extends ContentView {
|
|
|
2406
2469
|
// inside an uneditable node, and not bring it back when we
|
|
2407
2470
|
// move the cursor to its proper position. This tries to
|
|
2408
2471
|
// restore the keyboard by cycling focus.
|
|
2409
|
-
if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) &&
|
|
2472
|
+
if (browser.android && browser.chrome && this.dom.contains(domSel.focusNode) &&
|
|
2473
|
+
inUneditable(domSel.focusNode, this.dom)) {
|
|
2410
2474
|
this.dom.blur();
|
|
2411
2475
|
this.dom.focus({ preventScroll: true });
|
|
2412
2476
|
}
|
|
@@ -2449,7 +2513,7 @@ class DocView extends ContentView {
|
|
|
2449
2513
|
this.impreciseHead = head.precise ? null : new DOMPos(domSel.focusNode, domSel.focusOffset);
|
|
2450
2514
|
}
|
|
2451
2515
|
enforceCursorAssoc() {
|
|
2452
|
-
if (this.
|
|
2516
|
+
if (this.compositionDeco.size)
|
|
2453
2517
|
return;
|
|
2454
2518
|
let cursor = this.view.state.selection.main;
|
|
2455
2519
|
let sel = getSelection(this.root);
|
|
@@ -2674,7 +2738,8 @@ function computeCompositionDeco(view, changes) {
|
|
|
2674
2738
|
topNode = cView.dom;
|
|
2675
2739
|
}
|
|
2676
2740
|
let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
|
|
2677
|
-
let text =
|
|
2741
|
+
let { state } = view, text = topNode.nodeType == 3 ? topNode.nodeValue :
|
|
2742
|
+
new DOMReader([], view).readRange(topNode.firstChild, null).text;
|
|
2678
2743
|
if (newTo - newFrom < text.length) {
|
|
2679
2744
|
if (state.sliceDoc(newFrom, Math.min(state.doc.length, newFrom + text.length)) == text)
|
|
2680
2745
|
newTo = newFrom + text.length;
|
|
@@ -2878,21 +2943,29 @@ function domPosInText(node, x, y) {
|
|
|
2878
2943
|
function posAtCoords(view, { x, y }, precise, bias = -1) {
|
|
2879
2944
|
var _a;
|
|
2880
2945
|
let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
|
|
2881
|
-
let
|
|
2882
|
-
|
|
2883
|
-
|
|
2946
|
+
let block, yOffset = y - docTop, { docHeight } = view.viewState;
|
|
2947
|
+
if (yOffset < 0 || yOffset > docHeight) {
|
|
2948
|
+
if (precise)
|
|
2949
|
+
return null;
|
|
2950
|
+
yOffset = yOffset < 0 ? 0 : docHeight;
|
|
2951
|
+
}
|
|
2952
|
+
// Scan for a text block near the queried y position
|
|
2953
|
+
for (let halfLine = view.defaultLineHeight / 2, bounced = false;;) {
|
|
2884
2954
|
block = view.elementAtHeight(yOffset);
|
|
2885
|
-
if (block.
|
|
2886
|
-
|
|
2887
|
-
|
|
2955
|
+
if (block.type == BlockType.Text)
|
|
2956
|
+
break;
|
|
2957
|
+
for (;;) {
|
|
2958
|
+
// Move the y position out of this block
|
|
2959
|
+
yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
|
|
2960
|
+
if (yOffset >= 0 && yOffset <= docHeight)
|
|
2961
|
+
break;
|
|
2962
|
+
// If the document consists entirely of replaced widgets, we
|
|
2963
|
+
// won't find a text block, so return 0
|
|
2888
2964
|
if (bounced)
|
|
2889
2965
|
return precise ? null : 0;
|
|
2890
|
-
|
|
2891
|
-
|
|
2966
|
+
bounced = true;
|
|
2967
|
+
bias = -bias;
|
|
2892
2968
|
}
|
|
2893
|
-
if (block.type == BlockType.Text)
|
|
2894
|
-
break;
|
|
2895
|
-
yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
|
|
2896
2969
|
}
|
|
2897
2970
|
y = docTop + yOffset;
|
|
2898
2971
|
let lineStart = block.from;
|
|
@@ -3050,14 +3123,6 @@ class InputState {
|
|
|
3050
3123
|
constructor(view) {
|
|
3051
3124
|
this.lastKeyCode = 0;
|
|
3052
3125
|
this.lastKeyTime = 0;
|
|
3053
|
-
// On Chrome Android, backspace near widgets is just completely
|
|
3054
|
-
// broken, and there are no key events, so we need to handle the
|
|
3055
|
-
// beforeinput event. Deleting stuff will often create a flurry of
|
|
3056
|
-
// events, and interrupting it before it is done just makes
|
|
3057
|
-
// subsequent events even more broken, so again, we hold off doing
|
|
3058
|
-
// anything until the browser is finished with whatever it is trying
|
|
3059
|
-
// to do.
|
|
3060
|
-
this.pendingAndroidKey = undefined;
|
|
3061
3126
|
// On iOS, some keys need to have their default behavior happen
|
|
3062
3127
|
// (after which we retroactively handle them and reset the DOM) to
|
|
3063
3128
|
// avoid messing up the virtual keyboard state.
|
|
@@ -3126,22 +3191,15 @@ class InputState {
|
|
|
3126
3191
|
}
|
|
3127
3192
|
runCustomHandlers(type, view, event) {
|
|
3128
3193
|
for (let set of this.customHandlers) {
|
|
3129
|
-
let handler = set.handlers[type]
|
|
3194
|
+
let handler = set.handlers[type];
|
|
3130
3195
|
if (handler) {
|
|
3131
3196
|
try {
|
|
3132
|
-
|
|
3197
|
+
if (handler.call(set.plugin, event, view) || event.defaultPrevented)
|
|
3198
|
+
return true;
|
|
3133
3199
|
}
|
|
3134
3200
|
catch (e) {
|
|
3135
3201
|
logException(view.state, e);
|
|
3136
3202
|
}
|
|
3137
|
-
if (handled || event.defaultPrevented) {
|
|
3138
|
-
// Chrome for Android often applies a bunch of nonsensical
|
|
3139
|
-
// DOM changes after an enter press, even when
|
|
3140
|
-
// preventDefault-ed. This tries to ignore those.
|
|
3141
|
-
if (browser.android && type == "keydown" && event.keyCode == 13)
|
|
3142
|
-
view.observer.flushSoon();
|
|
3143
|
-
return true;
|
|
3144
|
-
}
|
|
3145
3203
|
}
|
|
3146
3204
|
}
|
|
3147
3205
|
return false;
|
|
@@ -3165,6 +3223,16 @@ class InputState {
|
|
|
3165
3223
|
this.lastKeyTime = Date.now();
|
|
3166
3224
|
if (this.screenKeyEvent(view, event))
|
|
3167
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
|
+
}
|
|
3168
3236
|
// Prevent the default behavior of Enter on iOS makes the
|
|
3169
3237
|
// virtual keyboard get stuck in the wrong (lowercase)
|
|
3170
3238
|
// state. So we let it go through, and then, in
|
|
@@ -3186,24 +3254,6 @@ class InputState {
|
|
|
3186
3254
|
this.pendingIOSKey = undefined;
|
|
3187
3255
|
return dispatchKey(view.contentDOM, key.key, key.keyCode);
|
|
3188
3256
|
}
|
|
3189
|
-
// This causes the DOM observer to pause for a bit, and sets an
|
|
3190
|
-
// animation frame (which seems the most reliable way to detect
|
|
3191
|
-
// 'Chrome is done flailing about messing with the DOM') to fire a
|
|
3192
|
-
// fake key event and re-sync the view again.
|
|
3193
|
-
setPendingAndroidKey(view, pending) {
|
|
3194
|
-
this.pendingAndroidKey = pending;
|
|
3195
|
-
requestAnimationFrame(() => {
|
|
3196
|
-
let key = this.pendingAndroidKey;
|
|
3197
|
-
if (!key)
|
|
3198
|
-
return;
|
|
3199
|
-
this.pendingAndroidKey = undefined;
|
|
3200
|
-
view.observer.processRecords();
|
|
3201
|
-
let startState = view.state;
|
|
3202
|
-
dispatchKey(view.contentDOM, key.key, key.keyCode);
|
|
3203
|
-
if (view.state == startState)
|
|
3204
|
-
view.docView.reset(true);
|
|
3205
|
-
});
|
|
3206
|
-
}
|
|
3207
3257
|
ignoreDuringComposition(event) {
|
|
3208
3258
|
if (!/^key/.test(event.type))
|
|
3209
3259
|
return false;
|
|
@@ -3233,10 +3283,10 @@ class InputState {
|
|
|
3233
3283
|
return (event.type == "keydown" && event.keyCode != 229) ||
|
|
3234
3284
|
event.type == "compositionend" && !browser.ios;
|
|
3235
3285
|
}
|
|
3236
|
-
startMouseSelection(
|
|
3286
|
+
startMouseSelection(mouseSelection) {
|
|
3237
3287
|
if (this.mouseSelection)
|
|
3238
3288
|
this.mouseSelection.destroy();
|
|
3239
|
-
this.mouseSelection =
|
|
3289
|
+
this.mouseSelection = mouseSelection;
|
|
3240
3290
|
}
|
|
3241
3291
|
update(update) {
|
|
3242
3292
|
if (this.mouseSelection)
|
|
@@ -3257,10 +3307,10 @@ const PendingKeys = [
|
|
|
3257
3307
|
// Key codes for modifier keys
|
|
3258
3308
|
const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
|
|
3259
3309
|
class MouseSelection {
|
|
3260
|
-
constructor(
|
|
3261
|
-
this.inputState = inputState;
|
|
3310
|
+
constructor(view, startEvent, style, mustSelect) {
|
|
3262
3311
|
this.view = view;
|
|
3263
3312
|
this.style = style;
|
|
3313
|
+
this.mustSelect = mustSelect;
|
|
3264
3314
|
this.lastEvent = startEvent;
|
|
3265
3315
|
let doc = view.contentDOM.ownerDocument;
|
|
3266
3316
|
doc.addEventListener("mousemove", this.move = this.move.bind(this));
|
|
@@ -3294,16 +3344,18 @@ class MouseSelection {
|
|
|
3294
3344
|
let doc = this.view.contentDOM.ownerDocument;
|
|
3295
3345
|
doc.removeEventListener("mousemove", this.move);
|
|
3296
3346
|
doc.removeEventListener("mouseup", this.up);
|
|
3297
|
-
this.inputState.mouseSelection = null;
|
|
3347
|
+
this.view.inputState.mouseSelection = null;
|
|
3298
3348
|
}
|
|
3299
3349
|
select(event) {
|
|
3300
3350
|
let selection = this.style.get(event, this.extend, this.multiple);
|
|
3301
|
-
if (!selection.eq(this.view.state.selection) ||
|
|
3351
|
+
if (this.mustSelect || !selection.eq(this.view.state.selection) ||
|
|
3352
|
+
selection.main.assoc != this.view.state.selection.main.assoc)
|
|
3302
3353
|
this.view.dispatch({
|
|
3303
3354
|
selection,
|
|
3304
3355
|
userEvent: "select.pointer",
|
|
3305
3356
|
scrollIntoView: true
|
|
3306
3357
|
});
|
|
3358
|
+
this.mustSelect = false;
|
|
3307
3359
|
}
|
|
3308
3360
|
update(update) {
|
|
3309
3361
|
if (update.docChanged && this.dragging)
|
|
@@ -3422,9 +3474,10 @@ handlers.mousedown = (view, event) => {
|
|
|
3422
3474
|
if (!style && event.button == 0)
|
|
3423
3475
|
style = basicMouseSelection(view, event);
|
|
3424
3476
|
if (style) {
|
|
3425
|
-
|
|
3477
|
+
let mustFocus = view.root.activeElement != view.contentDOM;
|
|
3478
|
+
if (mustFocus)
|
|
3426
3479
|
view.observer.ignore(() => focusPreventScroll(view.contentDOM));
|
|
3427
|
-
view.inputState.startMouseSelection(view, event, style);
|
|
3480
|
+
view.inputState.startMouseSelection(new MouseSelection(view, event, style, mustFocus));
|
|
3428
3481
|
}
|
|
3429
3482
|
};
|
|
3430
3483
|
function rangeForClick(view, pos, bias, type) {
|
|
@@ -3679,12 +3732,12 @@ handlers.compositionstart = handlers.compositionupdate = view => {
|
|
|
3679
3732
|
if (view.inputState.compositionFirstChange == null)
|
|
3680
3733
|
view.inputState.compositionFirstChange = true;
|
|
3681
3734
|
if (view.inputState.composing < 0) {
|
|
3735
|
+
// FIXME possibly set a timeout to clear it again on Android
|
|
3736
|
+
view.inputState.composing = 0;
|
|
3682
3737
|
if (view.docView.compositionDeco.size) {
|
|
3683
3738
|
view.observer.flush();
|
|
3684
3739
|
forceClearComposition(view, true);
|
|
3685
3740
|
}
|
|
3686
|
-
// FIXME possibly set a timeout to clear it again on Android
|
|
3687
|
-
view.inputState.composing = 0;
|
|
3688
3741
|
}
|
|
3689
3742
|
};
|
|
3690
3743
|
handlers.compositionend = view => {
|
|
@@ -3710,7 +3763,7 @@ handlers.beforeinput = (view, event) => {
|
|
|
3710
3763
|
// seems to do nothing at all on Chrome).
|
|
3711
3764
|
let pending;
|
|
3712
3765
|
if (browser.chrome && browser.android && (pending = PendingKeys.find(key => key.inputType == event.inputType))) {
|
|
3713
|
-
view.
|
|
3766
|
+
view.observer.delayAndroidKey(pending.key, pending.keyCode);
|
|
3714
3767
|
if (pending.key == "Backspace" || pending.key == "Delete") {
|
|
3715
3768
|
let startViewHeight = ((_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) || 0;
|
|
3716
3769
|
setTimeout(() => {
|
|
@@ -4415,8 +4468,8 @@ function visiblePixelRange(dom, paddingTop) {
|
|
|
4415
4468
|
break;
|
|
4416
4469
|
}
|
|
4417
4470
|
}
|
|
4418
|
-
return { left: left - rect.left, right: right - rect.left,
|
|
4419
|
-
top: top - (rect.top + paddingTop), bottom: bottom - (rect.top + paddingTop) };
|
|
4471
|
+
return { left: left - rect.left, right: Math.max(left, right) - rect.left,
|
|
4472
|
+
top: top - (rect.top + paddingTop), bottom: Math.max(top, bottom) - (rect.top + paddingTop) };
|
|
4420
4473
|
}
|
|
4421
4474
|
// Line gaps are placeholder widgets used to hide pieces of overlong
|
|
4422
4475
|
// lines within the viewport, as a kludge to keep the editor
|
|
@@ -4545,9 +4598,9 @@ class ViewState {
|
|
|
4545
4598
|
let updateLines = !update.changes.empty || (update.flags & 2 /* Height */) ||
|
|
4546
4599
|
viewport.from != this.viewport.from || viewport.to != this.viewport.to;
|
|
4547
4600
|
this.viewport = viewport;
|
|
4601
|
+
this.updateForViewport();
|
|
4548
4602
|
if (updateLines)
|
|
4549
4603
|
this.updateViewportLines();
|
|
4550
|
-
this.updateForViewport();
|
|
4551
4604
|
if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
|
|
4552
4605
|
this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
|
|
4553
4606
|
update.flags |= this.computeVisibleRanges();
|
|
@@ -4579,7 +4632,12 @@ class ViewState {
|
|
|
4579
4632
|
let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 } : visiblePixelRange(dom, this.paddingTop);
|
|
4580
4633
|
let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
|
|
4581
4634
|
this.pixelViewport = pixelViewport;
|
|
4582
|
-
|
|
4635
|
+
let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
|
|
4636
|
+
if (inView != this.inView) {
|
|
4637
|
+
this.inView = inView;
|
|
4638
|
+
if (inView)
|
|
4639
|
+
measureContent = true;
|
|
4640
|
+
}
|
|
4583
4641
|
if (!this.inView)
|
|
4584
4642
|
return 0;
|
|
4585
4643
|
if (measureContent) {
|
|
@@ -4616,9 +4674,9 @@ class ViewState {
|
|
|
4616
4674
|
this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to);
|
|
4617
4675
|
if (viewportChange)
|
|
4618
4676
|
this.viewport = this.getViewport(bias, this.scrollTarget);
|
|
4677
|
+
this.updateForViewport();
|
|
4619
4678
|
if ((result & 2 /* Height */) || viewportChange)
|
|
4620
4679
|
this.updateViewportLines();
|
|
4621
|
-
this.updateForViewport();
|
|
4622
4680
|
if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
|
|
4623
4681
|
this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
|
|
4624
4682
|
result |= this.computeVisibleRanges();
|
|
@@ -4643,7 +4701,7 @@ class ViewState {
|
|
|
4643
4701
|
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);
|
|
4644
4702
|
// If scrollTarget is given, make sure the viewport includes that position
|
|
4645
4703
|
if (scrollTarget) {
|
|
4646
|
-
let { head } = scrollTarget.range, viewHeight =
|
|
4704
|
+
let { head } = scrollTarget.range, viewHeight = this.editorHeight;
|
|
4647
4705
|
if (head < viewport.from || head > viewport.to) {
|
|
4648
4706
|
let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
|
|
4649
4707
|
if (scrollTarget.center)
|
|
@@ -4664,6 +4722,8 @@ class ViewState {
|
|
|
4664
4722
|
// Checks if a given viewport covers the visible part of the
|
|
4665
4723
|
// document and not too much beyond that.
|
|
4666
4724
|
viewportIsAppropriate({ from, to }, bias = 0) {
|
|
4725
|
+
if (!this.inView)
|
|
4726
|
+
return true;
|
|
4667
4727
|
let { top } = this.heightMap.lineAt(from, QueryType.ByPos, this.state.doc, 0, 0);
|
|
4668
4728
|
let { bottom } = this.heightMap.lineAt(to, QueryType.ByPos, this.state.doc, 0, 0);
|
|
4669
4729
|
let { visibleTop, visibleBottom } = this;
|
|
@@ -4770,8 +4830,11 @@ class ViewState {
|
|
|
4770
4830
|
elementAtHeight(height) {
|
|
4771
4831
|
return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height), this.state.doc, 0, 0), this.scaler);
|
|
4772
4832
|
}
|
|
4833
|
+
get docHeight() {
|
|
4834
|
+
return this.scaler.toDOM(this.heightMap.height);
|
|
4835
|
+
}
|
|
4773
4836
|
get contentHeight() {
|
|
4774
|
-
return this.
|
|
4837
|
+
return this.docHeight + this.paddingTop + this.paddingBottom;
|
|
4775
4838
|
}
|
|
4776
4839
|
}
|
|
4777
4840
|
class Viewport {
|
|
@@ -5001,11 +5064,13 @@ const baseTheme = /*@__PURE__*/buildTheme("." + baseThemeID, {
|
|
|
5001
5064
|
// recomputation.
|
|
5002
5065
|
"@keyframes cm-blink": { "0%": {}, "50%": { visibility: "hidden" }, "100%": {} },
|
|
5003
5066
|
"@keyframes cm-blink2": { "0%": {}, "50%": { visibility: "hidden" }, "100%": {} },
|
|
5004
|
-
".cm-cursor": {
|
|
5067
|
+
".cm-cursor, .cm-dropCursor": {
|
|
5005
5068
|
position: "absolute",
|
|
5006
5069
|
borderLeft: "1.2px solid black",
|
|
5007
5070
|
marginLeft: "-0.6px",
|
|
5008
5071
|
pointerEvents: "none",
|
|
5072
|
+
},
|
|
5073
|
+
".cm-cursor": {
|
|
5009
5074
|
display: "none"
|
|
5010
5075
|
},
|
|
5011
5076
|
"&dark .cm-cursor": {
|
|
@@ -5093,6 +5158,7 @@ class DOMObserver {
|
|
|
5093
5158
|
this.delayedFlush = -1;
|
|
5094
5159
|
this.resizeTimeout = -1;
|
|
5095
5160
|
this.queue = [];
|
|
5161
|
+
this.delayedAndroidKey = null;
|
|
5096
5162
|
this.scrollTargets = [];
|
|
5097
5163
|
this.intersection = null;
|
|
5098
5164
|
this.resize = null;
|
|
@@ -5146,7 +5212,7 @@ class DOMObserver {
|
|
|
5146
5212
|
this.intersection = new IntersectionObserver(entries => {
|
|
5147
5213
|
if (this.parentCheck < 0)
|
|
5148
5214
|
this.parentCheck = setTimeout(this.listenForScroll.bind(this), 1000);
|
|
5149
|
-
if (entries.length > 0 && entries[entries.length - 1].intersectionRatio > 0 != this.intersecting) {
|
|
5215
|
+
if (entries.length > 0 && (entries[entries.length - 1].intersectionRatio > 0) != this.intersecting) {
|
|
5150
5216
|
this.intersecting = !this.intersecting;
|
|
5151
5217
|
if (this.intersecting != this.view.inView)
|
|
5152
5218
|
this.onScrollChanged(document.createEvent("Event"));
|
|
@@ -5176,7 +5242,7 @@ class DOMObserver {
|
|
|
5176
5242
|
}
|
|
5177
5243
|
}
|
|
5178
5244
|
onSelectionChange(event) {
|
|
5179
|
-
if (!this.readSelectionRange())
|
|
5245
|
+
if (!this.readSelectionRange() || this.delayedAndroidKey)
|
|
5180
5246
|
return;
|
|
5181
5247
|
let { view } = this, sel = this.selectionRange;
|
|
5182
5248
|
if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
|
|
@@ -5272,6 +5338,32 @@ class DOMObserver {
|
|
|
5272
5338
|
this.queue.length = 0;
|
|
5273
5339
|
this.selectionChanged = false;
|
|
5274
5340
|
}
|
|
5341
|
+
// Chrome Android, especially in combination with GBoard, not only
|
|
5342
|
+
// doesn't reliably fire regular key events, but also often
|
|
5343
|
+
// surrounds the effect of enter or backspace with a bunch of
|
|
5344
|
+
// composition events that, when interrupted, cause text duplication
|
|
5345
|
+
// or other kinds of corruption. This hack makes the editor back off
|
|
5346
|
+
// from handling DOM changes for a moment when such a key is
|
|
5347
|
+
// detected (via beforeinput or keydown), and then dispatches the
|
|
5348
|
+
// key event, throwing away the DOM changes if it gets handled.
|
|
5349
|
+
delayAndroidKey(key, keyCode) {
|
|
5350
|
+
if (!this.delayedAndroidKey)
|
|
5351
|
+
requestAnimationFrame(() => {
|
|
5352
|
+
let key = this.delayedAndroidKey;
|
|
5353
|
+
this.delayedAndroidKey = null;
|
|
5354
|
+
let startState = this.view.state;
|
|
5355
|
+
if (dispatchKey(this.view.contentDOM, key.key, key.keyCode))
|
|
5356
|
+
this.processRecords();
|
|
5357
|
+
else
|
|
5358
|
+
this.flush();
|
|
5359
|
+
if (this.view.state == startState)
|
|
5360
|
+
this.view.update([]);
|
|
5361
|
+
});
|
|
5362
|
+
// Since backspace beforeinput is sometimes signalled spuriously,
|
|
5363
|
+
// Enter always takes precedence.
|
|
5364
|
+
if (!this.delayedAndroidKey || key == "Enter")
|
|
5365
|
+
this.delayedAndroidKey = { key, keyCode };
|
|
5366
|
+
}
|
|
5275
5367
|
flushSoon() {
|
|
5276
5368
|
if (this.delayedFlush < 0)
|
|
5277
5369
|
this.delayedFlush = window.setTimeout(() => { this.delayedFlush = -1; this.flush(); }, 20);
|
|
@@ -5308,13 +5400,13 @@ class DOMObserver {
|
|
|
5308
5400
|
}
|
|
5309
5401
|
// Apply pending changes, if any
|
|
5310
5402
|
flush(readSelection = true) {
|
|
5311
|
-
if (readSelection)
|
|
5312
|
-
this.readSelectionRange();
|
|
5313
5403
|
// Completely hold off flushing when pending keys are set—the code
|
|
5314
5404
|
// managing those will make sure processRecords is called and the
|
|
5315
5405
|
// view is resynchronized after
|
|
5316
|
-
if (this.delayedFlush >= 0 || this.
|
|
5406
|
+
if (this.delayedFlush >= 0 || this.delayedAndroidKey)
|
|
5317
5407
|
return;
|
|
5408
|
+
if (readSelection)
|
|
5409
|
+
this.readSelectionRange();
|
|
5318
5410
|
let { from, to, typeOver } = this.processRecords();
|
|
5319
5411
|
let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
|
|
5320
5412
|
if (from < 0 && !newSel)
|
|
@@ -5324,7 +5416,7 @@ class DOMObserver {
|
|
|
5324
5416
|
this.onChange(from, to, typeOver);
|
|
5325
5417
|
// The view wasn't updated
|
|
5326
5418
|
if (this.view.state == startState)
|
|
5327
|
-
this.view.
|
|
5419
|
+
this.view.update([]);
|
|
5328
5420
|
}
|
|
5329
5421
|
readMutation(rec) {
|
|
5330
5422
|
let cView = this.view.docView.nearest(rec.target);
|
|
@@ -5543,76 +5635,6 @@ function findDiff(a, b, preferredPos, preferredSide) {
|
|
|
5543
5635
|
}
|
|
5544
5636
|
return { from, toA, toB };
|
|
5545
5637
|
}
|
|
5546
|
-
class DOMReader {
|
|
5547
|
-
constructor(points, view) {
|
|
5548
|
-
this.points = points;
|
|
5549
|
-
this.view = view;
|
|
5550
|
-
this.text = "";
|
|
5551
|
-
this.lineBreak = view.state.lineBreak;
|
|
5552
|
-
}
|
|
5553
|
-
readRange(start, end) {
|
|
5554
|
-
if (!start)
|
|
5555
|
-
return;
|
|
5556
|
-
let parent = start.parentNode;
|
|
5557
|
-
for (let cur = start;;) {
|
|
5558
|
-
this.findPointBefore(parent, cur);
|
|
5559
|
-
this.readNode(cur);
|
|
5560
|
-
let next = cur.nextSibling;
|
|
5561
|
-
if (next == end)
|
|
5562
|
-
break;
|
|
5563
|
-
let view = ContentView.get(cur), nextView = ContentView.get(next);
|
|
5564
|
-
if (view && nextView ? view.breakAfter :
|
|
5565
|
-
(view ? view.breakAfter : isBlockElement(cur)) ||
|
|
5566
|
-
(isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
|
|
5567
|
-
this.text += this.lineBreak;
|
|
5568
|
-
cur = next;
|
|
5569
|
-
}
|
|
5570
|
-
this.findPointBefore(parent, end);
|
|
5571
|
-
}
|
|
5572
|
-
readNode(node) {
|
|
5573
|
-
if (node.cmIgnore)
|
|
5574
|
-
return;
|
|
5575
|
-
let view = ContentView.get(node);
|
|
5576
|
-
let fromView = view && view.overrideDOMText;
|
|
5577
|
-
let text;
|
|
5578
|
-
if (fromView != null)
|
|
5579
|
-
text = fromView.sliceString(0, undefined, this.lineBreak);
|
|
5580
|
-
else if (node.nodeType == 3)
|
|
5581
|
-
text = node.nodeValue;
|
|
5582
|
-
else if (node.nodeName == "BR")
|
|
5583
|
-
text = node.nextSibling ? this.lineBreak : "";
|
|
5584
|
-
else if (node.nodeType == 1)
|
|
5585
|
-
this.readRange(node.firstChild, null);
|
|
5586
|
-
if (text != null) {
|
|
5587
|
-
this.findPointIn(node, text.length);
|
|
5588
|
-
this.text += text;
|
|
5589
|
-
// Chrome inserts two newlines when pressing shift-enter at the
|
|
5590
|
-
// end of a line. This drops one of those.
|
|
5591
|
-
if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
|
|
5592
|
-
this.text = this.text.slice(0, -1);
|
|
5593
|
-
}
|
|
5594
|
-
}
|
|
5595
|
-
findPointBefore(node, next) {
|
|
5596
|
-
for (let point of this.points)
|
|
5597
|
-
if (point.node == node && node.childNodes[point.offset] == next)
|
|
5598
|
-
point.pos = this.text.length;
|
|
5599
|
-
}
|
|
5600
|
-
findPointIn(node, maxLen) {
|
|
5601
|
-
for (let point of this.points)
|
|
5602
|
-
if (point.node == node)
|
|
5603
|
-
point.pos = this.text.length + Math.min(point.offset, maxLen);
|
|
5604
|
-
}
|
|
5605
|
-
}
|
|
5606
|
-
function isBlockElement(node) {
|
|
5607
|
-
return node.nodeType == 1 && /^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(node.nodeName);
|
|
5608
|
-
}
|
|
5609
|
-
class DOMPoint {
|
|
5610
|
-
constructor(node, offset) {
|
|
5611
|
-
this.node = node;
|
|
5612
|
-
this.offset = offset;
|
|
5613
|
-
this.pos = -1;
|
|
5614
|
-
}
|
|
5615
|
-
}
|
|
5616
5638
|
function selectionPoints(view) {
|
|
5617
5639
|
let result = [];
|
|
5618
5640
|
if (view.root.activeElement != view.contentDOM)
|
|
@@ -5898,7 +5920,9 @@ class EditorView {
|
|
|
5898
5920
|
if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
|
|
5899
5921
|
break;
|
|
5900
5922
|
if (i > 5) {
|
|
5901
|
-
console.warn(this.measureRequests.length
|
|
5923
|
+
console.warn(this.measureRequests.length
|
|
5924
|
+
? "Measure loop restarted more than 5 times"
|
|
5925
|
+
: "Viewport failed to stabilize");
|
|
5902
5926
|
break;
|
|
5903
5927
|
}
|
|
5904
5928
|
let measuring = [];
|
|
@@ -5944,7 +5968,8 @@ class EditorView {
|
|
|
5944
5968
|
}
|
|
5945
5969
|
if (redrawn)
|
|
5946
5970
|
this.docView.updateSelection(true);
|
|
5947
|
-
if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
|
|
5971
|
+
if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
|
|
5972
|
+
this.measureRequests.length == 0)
|
|
5948
5973
|
break;
|
|
5949
5974
|
}
|
|
5950
5975
|
}
|
|
@@ -6236,6 +6261,11 @@ class EditorView {
|
|
|
6236
6261
|
Find the DOM parent node and offset (child offset if `node` is
|
|
6237
6262
|
an element, character offset when it is a text node) at the
|
|
6238
6263
|
given document position.
|
|
6264
|
+
|
|
6265
|
+
Note that for positions that aren't currently in
|
|
6266
|
+
`visibleRanges`, the resulting DOM position isn't necessarily
|
|
6267
|
+
meaningful (it may just point before or after a placeholder
|
|
6268
|
+
element).
|
|
6239
6269
|
*/
|
|
6240
6270
|
domAtPos(pos) {
|
|
6241
6271
|
return this.docView.domAtPos(pos);
|
|
@@ -6876,7 +6906,7 @@ function measureRange(view, range) {
|
|
|
6876
6906
|
let ltr = view.textDirection == Direction.LTR;
|
|
6877
6907
|
let content = view.contentDOM, contentRect = content.getBoundingClientRect(), base = getBase(view);
|
|
6878
6908
|
let lineStyle = window.getComputedStyle(content.firstChild);
|
|
6879
|
-
let leftSide = contentRect.left + parseInt(lineStyle.paddingLeft);
|
|
6909
|
+
let leftSide = contentRect.left + parseInt(lineStyle.paddingLeft) + Math.min(0, parseInt(lineStyle.textIndent));
|
|
6880
6910
|
let rightSide = contentRect.right - parseInt(lineStyle.paddingRight);
|
|
6881
6911
|
let startBlock = blockAt(view, from), endBlock = blockAt(view, to);
|
|
6882
6912
|
let visualStart = startBlock.type == BlockType.Text ? startBlock : null;
|
|
@@ -6896,7 +6926,7 @@ function measureRange(view, range) {
|
|
|
6896
6926
|
let between = [];
|
|
6897
6927
|
if ((visualStart || startBlock).to < (visualEnd || endBlock).from - 1)
|
|
6898
6928
|
between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
|
|
6899
|
-
else if (top.bottom < bottom.top &&
|
|
6929
|
+
else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == BlockType.Text)
|
|
6900
6930
|
top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
|
|
6901
6931
|
return pieces(top).concat(between).concat(pieces(bottom));
|
|
6902
6932
|
}
|
|
@@ -6961,6 +6991,98 @@ function measureCursor(view, cursor, primary) {
|
|
|
6961
6991
|
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");
|
|
6962
6992
|
}
|
|
6963
6993
|
|
|
6994
|
+
const setDropCursorPos = /*@__PURE__*/StateEffect.define({
|
|
6995
|
+
map(pos, mapping) { return pos == null ? null : mapping.mapPos(pos); }
|
|
6996
|
+
});
|
|
6997
|
+
const dropCursorPos = /*@__PURE__*/StateField.define({
|
|
6998
|
+
create() { return null; },
|
|
6999
|
+
update(pos, tr) {
|
|
7000
|
+
if (pos != null)
|
|
7001
|
+
pos = tr.changes.mapPos(pos);
|
|
7002
|
+
return tr.effects.reduce((pos, e) => e.is(setDropCursorPos) ? e.value : pos, pos);
|
|
7003
|
+
}
|
|
7004
|
+
});
|
|
7005
|
+
const drawDropCursor = /*@__PURE__*/ViewPlugin.fromClass(class {
|
|
7006
|
+
constructor(view) {
|
|
7007
|
+
this.view = view;
|
|
7008
|
+
this.cursor = null;
|
|
7009
|
+
this.measureReq = { read: this.readPos.bind(this), write: this.drawCursor.bind(this) };
|
|
7010
|
+
}
|
|
7011
|
+
update(update) {
|
|
7012
|
+
var _a;
|
|
7013
|
+
let cursorPos = update.state.field(dropCursorPos);
|
|
7014
|
+
if (cursorPos == null) {
|
|
7015
|
+
if (this.cursor != null) {
|
|
7016
|
+
(_a = this.cursor) === null || _a === void 0 ? void 0 : _a.remove();
|
|
7017
|
+
this.cursor = null;
|
|
7018
|
+
}
|
|
7019
|
+
}
|
|
7020
|
+
else {
|
|
7021
|
+
if (!this.cursor) {
|
|
7022
|
+
this.cursor = this.view.scrollDOM.appendChild(document.createElement("div"));
|
|
7023
|
+
this.cursor.className = "cm-dropCursor";
|
|
7024
|
+
}
|
|
7025
|
+
if (update.startState.field(dropCursorPos) != cursorPos || update.docChanged || update.geometryChanged)
|
|
7026
|
+
this.view.requestMeasure(this.measureReq);
|
|
7027
|
+
}
|
|
7028
|
+
}
|
|
7029
|
+
readPos() {
|
|
7030
|
+
let pos = this.view.state.field(dropCursorPos);
|
|
7031
|
+
let rect = pos != null && this.view.coordsAtPos(pos);
|
|
7032
|
+
if (!rect)
|
|
7033
|
+
return null;
|
|
7034
|
+
let outer = this.view.scrollDOM.getBoundingClientRect();
|
|
7035
|
+
return {
|
|
7036
|
+
left: rect.left - outer.left + this.view.scrollDOM.scrollLeft,
|
|
7037
|
+
top: rect.top - outer.top + this.view.scrollDOM.scrollTop,
|
|
7038
|
+
height: rect.bottom - rect.top
|
|
7039
|
+
};
|
|
7040
|
+
}
|
|
7041
|
+
drawCursor(pos) {
|
|
7042
|
+
if (this.cursor) {
|
|
7043
|
+
if (pos) {
|
|
7044
|
+
this.cursor.style.left = pos.left + "px";
|
|
7045
|
+
this.cursor.style.top = pos.top + "px";
|
|
7046
|
+
this.cursor.style.height = pos.height + "px";
|
|
7047
|
+
}
|
|
7048
|
+
else {
|
|
7049
|
+
this.cursor.style.left = "-100000px";
|
|
7050
|
+
}
|
|
7051
|
+
}
|
|
7052
|
+
}
|
|
7053
|
+
destroy() {
|
|
7054
|
+
if (this.cursor)
|
|
7055
|
+
this.cursor.remove();
|
|
7056
|
+
}
|
|
7057
|
+
setDropPos(pos) {
|
|
7058
|
+
if (this.view.state.field(dropCursorPos) != pos)
|
|
7059
|
+
this.view.dispatch({ effects: setDropCursorPos.of(pos) });
|
|
7060
|
+
}
|
|
7061
|
+
}, {
|
|
7062
|
+
eventHandlers: {
|
|
7063
|
+
dragover(event) {
|
|
7064
|
+
this.setDropPos(this.view.posAtCoords({ x: event.clientX, y: event.clientY }));
|
|
7065
|
+
},
|
|
7066
|
+
dragleave(event) {
|
|
7067
|
+
if (event.target == this.view.contentDOM || !this.view.contentDOM.contains(event.relatedTarget))
|
|
7068
|
+
this.setDropPos(null);
|
|
7069
|
+
},
|
|
7070
|
+
dragend() {
|
|
7071
|
+
this.setDropPos(null);
|
|
7072
|
+
},
|
|
7073
|
+
drop() {
|
|
7074
|
+
this.setDropPos(null);
|
|
7075
|
+
}
|
|
7076
|
+
}
|
|
7077
|
+
});
|
|
7078
|
+
/**
|
|
7079
|
+
Draws a cursor at the current drop position when something is
|
|
7080
|
+
dragged over the editor.
|
|
7081
|
+
*/
|
|
7082
|
+
function dropCursor() {
|
|
7083
|
+
return [dropCursorPos, drawDropCursor];
|
|
7084
|
+
}
|
|
7085
|
+
|
|
6964
7086
|
function iterMatches(doc, re, from, to, f) {
|
|
6965
7087
|
re.lastIndex = 0;
|
|
6966
7088
|
for (let cursor = doc.iterRange(from, to), pos = from, m; !cursor.next().done; pos += cursor.value.length) {
|
|
@@ -6969,6 +7091,22 @@ function iterMatches(doc, re, from, to, f) {
|
|
|
6969
7091
|
f(pos + m.index, pos + m.index + m[0].length, m);
|
|
6970
7092
|
}
|
|
6971
7093
|
}
|
|
7094
|
+
function matchRanges(view, maxLength) {
|
|
7095
|
+
let visible = view.visibleRanges;
|
|
7096
|
+
if (visible.length == 1 && visible[0].from == view.viewport.from &&
|
|
7097
|
+
visible[0].to == view.viewport.to)
|
|
7098
|
+
return visible;
|
|
7099
|
+
let result = [];
|
|
7100
|
+
for (let { from, to } of visible) {
|
|
7101
|
+
from = Math.max(view.state.doc.lineAt(from).from, from - maxLength);
|
|
7102
|
+
to = Math.min(view.state.doc.lineAt(to).to, to + maxLength);
|
|
7103
|
+
if (result.length && result[result.length - 1].to >= from)
|
|
7104
|
+
result[result.length - 1].to = to;
|
|
7105
|
+
else
|
|
7106
|
+
result.push({ from, to });
|
|
7107
|
+
}
|
|
7108
|
+
return result;
|
|
7109
|
+
}
|
|
6972
7110
|
/**
|
|
6973
7111
|
Helper class used to make it easier to maintain decorations on
|
|
6974
7112
|
visible code that matches a given regular expression. To be used
|
|
@@ -6980,12 +7118,13 @@ class MatchDecorator {
|
|
|
6980
7118
|
Create a decorator.
|
|
6981
7119
|
*/
|
|
6982
7120
|
constructor(config) {
|
|
6983
|
-
let { regexp, decoration, boundary } = config;
|
|
7121
|
+
let { regexp, decoration, boundary, maxLength = 1000 } = config;
|
|
6984
7122
|
if (!regexp.global)
|
|
6985
7123
|
throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");
|
|
6986
7124
|
this.regexp = regexp;
|
|
6987
7125
|
this.getDeco = typeof decoration == "function" ? decoration : () => decoration;
|
|
6988
7126
|
this.boundary = boundary;
|
|
7127
|
+
this.maxLength = maxLength;
|
|
6989
7128
|
}
|
|
6990
7129
|
/**
|
|
6991
7130
|
Compute the full set of decorations for matches in the given
|
|
@@ -6994,7 +7133,7 @@ class MatchDecorator {
|
|
|
6994
7133
|
*/
|
|
6995
7134
|
createDeco(view) {
|
|
6996
7135
|
let build = new RangeSetBuilder();
|
|
6997
|
-
for (let { from, to } of view.
|
|
7136
|
+
for (let { from, to } of matchRanges(view, this.maxLength))
|
|
6998
7137
|
iterMatches(view.state.doc, this.regexp, from, to, (a, b, m) => build.add(a, b, this.getDeco(m, view, a)));
|
|
6999
7138
|
return build.finish();
|
|
7000
7139
|
}
|
|
@@ -7295,4 +7434,4 @@ function placeholder(content) {
|
|
|
7295
7434
|
*/
|
|
7296
7435
|
const __test = { HeightMap, HeightOracle, MeasuredHeights, QueryType, ChangedRange, computeOrder, moveVisually };
|
|
7297
7436
|
|
|
7298
|
-
export { BidiSpan, BlockInfo, BlockType, Decoration, Direction, EditorView, MatchDecorator, PluginField, PluginFieldProvider, ViewPlugin, ViewUpdate, WidgetType, __test, drawSelection, highlightActiveLine, highlightSpecialChars, keymap, logException, placeholder, runScopeHandlers, scrollPastEnd };
|
|
7437
|
+
export { BidiSpan, BlockInfo, BlockType, Decoration, Direction, EditorView, MatchDecorator, PluginField, PluginFieldProvider, ViewPlugin, ViewUpdate, WidgetType, __test, drawSelection, dropCursor, highlightActiveLine, highlightSpecialChars, keymap, logException, placeholder, runScopeHandlers, scrollPastEnd };
|