@codemirror/view 0.19.38 → 0.19.42
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 +50 -0
- package/dist/index.cjs +183 -79
- package/dist/index.d.ts +8 -1
- package/dist/index.js +184 -80
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,53 @@
|
|
|
1
|
+
## 0.19.42 (2022-02-05)
|
|
2
|
+
|
|
3
|
+
### Bug fixes
|
|
4
|
+
|
|
5
|
+
Fix a regression in cursor position determination after making an edit next to a widget.
|
|
6
|
+
|
|
7
|
+
## 0.19.41 (2022-02-04)
|
|
8
|
+
|
|
9
|
+
### Bug fixes
|
|
10
|
+
|
|
11
|
+
Fix an issue where the editor's view of its content height could go out of sync with the DOM when a line-wrapping editor had its width changed, causing wrapping to change.
|
|
12
|
+
|
|
13
|
+
Fix a bug that caused the editor to draw way too much content when scrolling to a position in an editor (much) taller than the window.
|
|
14
|
+
|
|
15
|
+
Report an error when a replace decoration from a plugin crosses a line break, rather than silently ignoring it.
|
|
16
|
+
|
|
17
|
+
Fix an issue where reading DOM changes was broken when `lineSeparator` contained more than one character.
|
|
18
|
+
|
|
19
|
+
Make ordering of replace and mark decorations with the same extent and inclusivness more predictable by giving replace decorations precedence.
|
|
20
|
+
|
|
21
|
+
Fix a bug where, on Chrome, replacement across line boundaries and next to widgets could cause bogus zero-width characters to appear in the content.
|
|
22
|
+
|
|
23
|
+
## 0.19.40 (2022-01-19)
|
|
24
|
+
|
|
25
|
+
### Bug fixes
|
|
26
|
+
|
|
27
|
+
Make composition input properly appear at secondary cursors (except when those are in the DOM node with the composition, in which case the browser won't allow us to intervene without aborting the composition).
|
|
28
|
+
|
|
29
|
+
Fix a bug that cause the editor to get confused about which content was visible after scrolling something into view.
|
|
30
|
+
|
|
31
|
+
Fix a bug where the dummy elements rendered around widgets could end up in a separate set of wrapping marks, and thus become visible.
|
|
32
|
+
|
|
33
|
+
`EditorView.moveVertically` now preserves the `assoc` property of the input range.
|
|
34
|
+
|
|
35
|
+
Get rid of gaps between selection elements drawn by `drawSelection`.
|
|
36
|
+
|
|
37
|
+
Fix an issue where replacing text next to a widget might leak bogus zero-width spaces into the document.
|
|
38
|
+
|
|
39
|
+
Avoid browser selection mishandling when a focused view has `setState` called by eagerly refocusing it.
|
|
40
|
+
|
|
41
|
+
## 0.19.39 (2022-01-06)
|
|
42
|
+
|
|
43
|
+
### Bug fixes
|
|
44
|
+
|
|
45
|
+
Make sure the editor signals a `geometryChanged` update when its width changes.
|
|
46
|
+
|
|
47
|
+
### New features
|
|
48
|
+
|
|
49
|
+
`EditorView.darkTheme` can now be queried to figure out whether the editor is using a dark theme.
|
|
50
|
+
|
|
1
51
|
## 0.19.38 (2022-01-05)
|
|
2
52
|
|
|
3
53
|
### Bug fixes
|
package/dist/index.cjs
CHANGED
|
@@ -602,9 +602,8 @@ function mergeChildrenInto(parent, from, to, insert, openStart, openEnd) {
|
|
|
602
602
|
replaceRange(parent, fromI, fromOff, toI, toOff, insert, 0, openStart, openEnd);
|
|
603
603
|
}
|
|
604
604
|
|
|
605
|
-
let
|
|
606
|
-
|
|
607
|
-
: [{ userAgent: "", vendor: "", platform: "" }, { documentElement: { style: {} } }];
|
|
605
|
+
let nav = typeof navigator != "undefined" ? navigator : { userAgent: "", vendor: "", platform: "" };
|
|
606
|
+
let doc = typeof document != "undefined" ? document : { documentElement: { style: {} } };
|
|
608
607
|
const ie_edge = /Edge\/(\d+)/.exec(nav.userAgent);
|
|
609
608
|
const ie_upto10 = /MSIE \d/.test(nav.userAgent);
|
|
610
609
|
const ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(nav.userAgent);
|
|
@@ -1131,8 +1130,8 @@ class Decoration extends rangeset.RangeValue {
|
|
|
1131
1130
|
static replace(spec) {
|
|
1132
1131
|
let block = !!spec.block;
|
|
1133
1132
|
let { start, end } = getInclusive(spec, block);
|
|
1134
|
-
let startSide =
|
|
1135
|
-
let endSide =
|
|
1133
|
+
let startSide = (start ? (block ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 400000000 /* NonIncStart */) - 1;
|
|
1134
|
+
let endSide = (end ? (block ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -500000000 /* NonIncEnd */) + 1;
|
|
1136
1135
|
return new PointDecoration(spec, startSide, endSide, block, spec.widget || null, true);
|
|
1137
1136
|
}
|
|
1138
1137
|
/**
|
|
@@ -1237,7 +1236,7 @@ function widgetsEq(a, b) {
|
|
|
1237
1236
|
}
|
|
1238
1237
|
function addRange(from, to, ranges, margin = 0) {
|
|
1239
1238
|
let last = ranges.length - 1;
|
|
1240
|
-
if (last >= 0 && ranges[last] + margin
|
|
1239
|
+
if (last >= 0 && ranges[last] + margin >= from)
|
|
1241
1240
|
ranges[last] = Math.max(ranges[last], to);
|
|
1242
1241
|
else
|
|
1243
1242
|
ranges.push(from, to);
|
|
@@ -1518,7 +1517,7 @@ class ContentBuilder {
|
|
|
1518
1517
|
}
|
|
1519
1518
|
}
|
|
1520
1519
|
let take = Math.min(this.text.length - this.textOff, length, 512 /* Chunk */);
|
|
1521
|
-
this.flushBuffer(active);
|
|
1520
|
+
this.flushBuffer(active.slice(0, openStart));
|
|
1522
1521
|
this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
|
|
1523
1522
|
this.atCursorPos = true;
|
|
1524
1523
|
this.textOff += take;
|
|
@@ -1577,11 +1576,13 @@ class ContentBuilder {
|
|
|
1577
1576
|
this.openStart = openStart;
|
|
1578
1577
|
}
|
|
1579
1578
|
filterPoint(from, to, value, index) {
|
|
1580
|
-
if (index
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1579
|
+
if (index < this.disallowBlockEffectsBelow && value instanceof PointDecoration) {
|
|
1580
|
+
if (value.block)
|
|
1581
|
+
throw new RangeError("Block decorations may not be specified via plugins");
|
|
1582
|
+
if (to > this.doc.lineAt(this.pos).to)
|
|
1583
|
+
throw new RangeError("Decorations that replace line breaks may not be specified via plugins");
|
|
1584
|
+
}
|
|
1585
|
+
return true;
|
|
1585
1586
|
}
|
|
1586
1587
|
static build(text, from, to, decorations, pluginDecorationLength) {
|
|
1587
1588
|
let builder = new ContentBuilder(text, from, to, pluginDecorationLength);
|
|
@@ -2305,12 +2306,18 @@ function moveVisually(line, order, dir, start, forward) {
|
|
|
2305
2306
|
return state.EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
|
|
2306
2307
|
}
|
|
2307
2308
|
|
|
2309
|
+
const LineBreakPlaceholder = "\uffff";
|
|
2308
2310
|
class DOMReader {
|
|
2309
|
-
constructor(points,
|
|
2311
|
+
constructor(points, state$1) {
|
|
2310
2312
|
this.points = points;
|
|
2311
|
-
this.view = view;
|
|
2312
2313
|
this.text = "";
|
|
2313
|
-
this.
|
|
2314
|
+
this.lineSeparator = state$1.facet(state.EditorState.lineSeparator);
|
|
2315
|
+
}
|
|
2316
|
+
append(text) {
|
|
2317
|
+
this.text += text;
|
|
2318
|
+
}
|
|
2319
|
+
lineBreak() {
|
|
2320
|
+
this.text += LineBreakPlaceholder;
|
|
2314
2321
|
}
|
|
2315
2322
|
readRange(start, end) {
|
|
2316
2323
|
if (!start)
|
|
@@ -2326,33 +2333,61 @@ class DOMReader {
|
|
|
2326
2333
|
if (view && nextView ? view.breakAfter :
|
|
2327
2334
|
(view ? view.breakAfter : isBlockElement(cur)) ||
|
|
2328
2335
|
(isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
|
|
2329
|
-
this.
|
|
2336
|
+
this.lineBreak();
|
|
2330
2337
|
cur = next;
|
|
2331
2338
|
}
|
|
2332
2339
|
this.findPointBefore(parent, end);
|
|
2333
2340
|
return this;
|
|
2334
2341
|
}
|
|
2342
|
+
readTextNode(node) {
|
|
2343
|
+
let text = node.nodeValue;
|
|
2344
|
+
for (let point of this.points)
|
|
2345
|
+
if (point.node == node)
|
|
2346
|
+
point.pos = this.text.length + Math.min(point.offset, text.length);
|
|
2347
|
+
for (let off = 0, re = this.lineSeparator ? null : /\r\n?|\n/g;;) {
|
|
2348
|
+
let nextBreak = -1, breakSize = 1, m;
|
|
2349
|
+
if (this.lineSeparator) {
|
|
2350
|
+
nextBreak = text.indexOf(this.lineSeparator, off);
|
|
2351
|
+
breakSize = this.lineSeparator.length;
|
|
2352
|
+
}
|
|
2353
|
+
else if (m = re.exec(text)) {
|
|
2354
|
+
nextBreak = m.index;
|
|
2355
|
+
breakSize = m[0].length;
|
|
2356
|
+
}
|
|
2357
|
+
this.append(text.slice(off, nextBreak < 0 ? text.length : nextBreak));
|
|
2358
|
+
if (nextBreak < 0)
|
|
2359
|
+
break;
|
|
2360
|
+
this.lineBreak();
|
|
2361
|
+
if (breakSize > 1)
|
|
2362
|
+
for (let point of this.points)
|
|
2363
|
+
if (point.node == node && point.pos > this.text.length)
|
|
2364
|
+
point.pos -= breakSize - 1;
|
|
2365
|
+
off = nextBreak + breakSize;
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2335
2368
|
readNode(node) {
|
|
2336
2369
|
if (node.cmIgnore)
|
|
2337
2370
|
return;
|
|
2338
2371
|
let view = ContentView.get(node);
|
|
2339
2372
|
let fromView = view && view.overrideDOMText;
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2373
|
+
if (fromView != null) {
|
|
2374
|
+
this.findPointInside(node, fromView.length);
|
|
2375
|
+
for (let i = fromView.iter(); !i.next().done;) {
|
|
2376
|
+
if (i.lineBreak)
|
|
2377
|
+
this.lineBreak();
|
|
2378
|
+
else
|
|
2379
|
+
this.append(i.value);
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
else if (node.nodeType == 3) {
|
|
2383
|
+
this.readTextNode(node);
|
|
2384
|
+
}
|
|
2385
|
+
else if (node.nodeName == "BR") {
|
|
2386
|
+
if (node.nextSibling)
|
|
2387
|
+
this.lineBreak();
|
|
2388
|
+
}
|
|
2389
|
+
else if (node.nodeType == 1) {
|
|
2348
2390
|
this.readRange(node.firstChild, null);
|
|
2349
|
-
if (text != null) {
|
|
2350
|
-
this.findPointIn(node, text.length);
|
|
2351
|
-
this.text += text;
|
|
2352
|
-
// Chrome inserts two newlines when pressing shift-enter at the
|
|
2353
|
-
// end of a line. This drops one of those.
|
|
2354
|
-
if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
|
|
2355
|
-
this.text = this.text.slice(0, -1);
|
|
2356
2391
|
}
|
|
2357
2392
|
}
|
|
2358
2393
|
findPointBefore(node, next) {
|
|
@@ -2360,10 +2395,10 @@ class DOMReader {
|
|
|
2360
2395
|
if (point.node == node && node.childNodes[point.offset] == next)
|
|
2361
2396
|
point.pos = this.text.length;
|
|
2362
2397
|
}
|
|
2363
|
-
|
|
2398
|
+
findPointInside(node, maxLen) {
|
|
2364
2399
|
for (let point of this.points)
|
|
2365
|
-
if (point.node == node)
|
|
2366
|
-
point.pos = this.text.length + Math.min(point.offset
|
|
2400
|
+
if (node.nodeType == 3 ? point.node == node : node.contains(point.node))
|
|
2401
|
+
point.pos = this.text.length + Math.min(maxLen, point.offset);
|
|
2367
2402
|
}
|
|
2368
2403
|
}
|
|
2369
2404
|
function isBlockElement(node) {
|
|
@@ -2768,51 +2803,57 @@ class BlockGapWidget extends WidgetType {
|
|
|
2768
2803
|
}
|
|
2769
2804
|
get estimatedHeight() { return this.height; }
|
|
2770
2805
|
}
|
|
2771
|
-
function
|
|
2806
|
+
function compositionSurroundingNode(view) {
|
|
2772
2807
|
let sel = view.observer.selectionRange;
|
|
2773
2808
|
let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
|
|
2774
2809
|
if (!textNode)
|
|
2775
|
-
return
|
|
2810
|
+
return null;
|
|
2776
2811
|
let cView = view.docView.nearest(textNode);
|
|
2777
2812
|
if (!cView)
|
|
2778
|
-
return
|
|
2779
|
-
let from, to, topNode = textNode;
|
|
2813
|
+
return null;
|
|
2780
2814
|
if (cView instanceof LineView) {
|
|
2815
|
+
let topNode = textNode;
|
|
2781
2816
|
while (topNode.parentNode != cView.dom)
|
|
2782
2817
|
topNode = topNode.parentNode;
|
|
2783
2818
|
let prev = topNode.previousSibling;
|
|
2784
2819
|
while (prev && !ContentView.get(prev))
|
|
2785
2820
|
prev = prev.previousSibling;
|
|
2786
|
-
|
|
2821
|
+
let pos = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
|
|
2822
|
+
return { from: pos, to: pos, node: topNode, text: textNode };
|
|
2787
2823
|
}
|
|
2788
2824
|
else {
|
|
2789
2825
|
for (;;) {
|
|
2790
2826
|
let { parent } = cView;
|
|
2791
2827
|
if (!parent)
|
|
2792
|
-
return
|
|
2828
|
+
return null;
|
|
2793
2829
|
if (parent instanceof LineView)
|
|
2794
2830
|
break;
|
|
2795
2831
|
cView = parent;
|
|
2796
2832
|
}
|
|
2797
|
-
from = cView.posAtStart;
|
|
2798
|
-
|
|
2799
|
-
topNode = cView.dom;
|
|
2833
|
+
let from = cView.posAtStart;
|
|
2834
|
+
return { from, to: from + cView.length, node: cView.dom, text: textNode };
|
|
2800
2835
|
}
|
|
2836
|
+
}
|
|
2837
|
+
function computeCompositionDeco(view, changes) {
|
|
2838
|
+
let surrounding = compositionSurroundingNode(view);
|
|
2839
|
+
if (!surrounding)
|
|
2840
|
+
return Decoration.none;
|
|
2841
|
+
let { from, to, node, text: textNode } = surrounding;
|
|
2801
2842
|
let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
|
|
2802
|
-
let { state } = view, text =
|
|
2803
|
-
new DOMReader([],
|
|
2843
|
+
let { state } = view, text = node.nodeType == 3 ? node.nodeValue :
|
|
2844
|
+
new DOMReader([], state).readRange(node.firstChild, null).text;
|
|
2804
2845
|
if (newTo - newFrom < text.length) {
|
|
2805
|
-
if (state.
|
|
2846
|
+
if (state.doc.sliceString(newFrom, Math.min(state.doc.length, newFrom + text.length), LineBreakPlaceholder) == text)
|
|
2806
2847
|
newTo = newFrom + text.length;
|
|
2807
|
-
else if (state.
|
|
2848
|
+
else if (state.doc.sliceString(Math.max(0, newTo - text.length), newTo, LineBreakPlaceholder) == text)
|
|
2808
2849
|
newFrom = newTo - text.length;
|
|
2809
2850
|
else
|
|
2810
2851
|
return Decoration.none;
|
|
2811
2852
|
}
|
|
2812
|
-
else if (state.
|
|
2853
|
+
else if (state.doc.sliceString(newFrom, newTo, LineBreakPlaceholder) != text) {
|
|
2813
2854
|
return Decoration.none;
|
|
2814
2855
|
}
|
|
2815
|
-
return Decoration.set(Decoration.replace({ widget: new CompositionWidget(
|
|
2856
|
+
return Decoration.set(Decoration.replace({ widget: new CompositionWidget(node, textNode) }).range(newFrom, newTo));
|
|
2816
2857
|
}
|
|
2817
2858
|
class CompositionWidget extends WidgetType {
|
|
2818
2859
|
constructor(top, text) {
|
|
@@ -3149,7 +3190,7 @@ function byGroup(view, pos, start) {
|
|
|
3149
3190
|
function moveVertically(view, start, forward, distance) {
|
|
3150
3191
|
let startPos = start.head, dir = forward ? 1 : -1;
|
|
3151
3192
|
if (startPos == (forward ? view.state.doc.length : 0))
|
|
3152
|
-
return state.EditorSelection.cursor(startPos);
|
|
3193
|
+
return state.EditorSelection.cursor(startPos, start.assoc);
|
|
3153
3194
|
let goal = start.goalColumn, startY;
|
|
3154
3195
|
let rect = view.contentDOM.getBoundingClientRect();
|
|
3155
3196
|
let startCoords = view.coordsAtPos(startPos), docTop = view.documentTop;
|
|
@@ -3170,7 +3211,7 @@ function moveVertically(view, start, forward, distance) {
|
|
|
3170
3211
|
let curY = startY + (dist + extra) * dir;
|
|
3171
3212
|
let pos = posAtCoords(view, { x: resolvedGoal, y: curY }, false, dir);
|
|
3172
3213
|
if (curY < rect.top || curY > rect.bottom || (dir < 0 ? pos < startPos : pos > startPos))
|
|
3173
|
-
return state.EditorSelection.cursor(pos,
|
|
3214
|
+
return state.EditorSelection.cursor(pos, start.assoc, undefined, goal);
|
|
3174
3215
|
}
|
|
3175
3216
|
}
|
|
3176
3217
|
function skipAtoms(view, oldPos, pos) {
|
|
@@ -4599,6 +4640,7 @@ class ViewState {
|
|
|
4599
4640
|
this.contentDOMWidth = 0;
|
|
4600
4641
|
this.contentDOMHeight = 0;
|
|
4601
4642
|
this.editorHeight = 0;
|
|
4643
|
+
this.editorWidth = 0;
|
|
4602
4644
|
this.heightOracle = new HeightOracle;
|
|
4603
4645
|
// See VP.MaxDOMHeight
|
|
4604
4646
|
this.scaler = IdScaler;
|
|
@@ -4681,6 +4723,12 @@ class ViewState {
|
|
|
4681
4723
|
let refresh = this.heightOracle.mustRefreshForStyle(whiteSpace, direction);
|
|
4682
4724
|
let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != dom.clientHeight;
|
|
4683
4725
|
let result = 0, bias = 0;
|
|
4726
|
+
if (this.editorWidth != view.scrollDOM.clientWidth) {
|
|
4727
|
+
if (oracle.lineWrapping)
|
|
4728
|
+
measureContent = true;
|
|
4729
|
+
this.editorWidth = view.scrollDOM.clientWidth;
|
|
4730
|
+
result |= 8 /* Geometry */;
|
|
4731
|
+
}
|
|
4684
4732
|
if (measureContent) {
|
|
4685
4733
|
this.mustMeasureContent = false;
|
|
4686
4734
|
this.contentDOMHeight = dom.clientHeight;
|
|
@@ -4693,7 +4741,8 @@ class ViewState {
|
|
|
4693
4741
|
}
|
|
4694
4742
|
}
|
|
4695
4743
|
// Pixel viewport
|
|
4696
|
-
let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 }
|
|
4744
|
+
let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 }
|
|
4745
|
+
: visiblePixelRange(dom, this.paddingTop);
|
|
4697
4746
|
let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
|
|
4698
4747
|
this.pixelViewport = pixelViewport;
|
|
4699
4748
|
let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
|
|
@@ -4704,11 +4753,16 @@ class ViewState {
|
|
|
4704
4753
|
}
|
|
4705
4754
|
if (!this.inView)
|
|
4706
4755
|
return 0;
|
|
4756
|
+
let contentWidth = dom.clientWidth;
|
|
4757
|
+
if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight) {
|
|
4758
|
+
this.contentDOMWidth = contentWidth;
|
|
4759
|
+
this.editorHeight = view.scrollDOM.clientHeight;
|
|
4760
|
+
result |= 8 /* Geometry */;
|
|
4761
|
+
}
|
|
4707
4762
|
if (measureContent) {
|
|
4708
4763
|
let lineHeights = view.docView.measureVisibleLineHeights();
|
|
4709
4764
|
if (oracle.mustRefreshForHeights(lineHeights))
|
|
4710
4765
|
refresh = true;
|
|
4711
|
-
let contentWidth = dom.clientWidth;
|
|
4712
4766
|
if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
|
|
4713
4767
|
let { lineHeight, charWidth } = view.docView.measureTextSize();
|
|
4714
4768
|
refresh = oracle.refresh(whiteSpace, direction, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
|
|
@@ -4717,14 +4771,6 @@ class ViewState {
|
|
|
4717
4771
|
result |= 8 /* Geometry */;
|
|
4718
4772
|
}
|
|
4719
4773
|
}
|
|
4720
|
-
if (this.contentDOMWidth != contentWidth) {
|
|
4721
|
-
this.contentDOMWidth = contentWidth;
|
|
4722
|
-
result |= 8 /* Geometry */;
|
|
4723
|
-
}
|
|
4724
|
-
if (this.editorHeight != view.scrollDOM.clientHeight) {
|
|
4725
|
-
this.editorHeight = view.scrollDOM.clientHeight;
|
|
4726
|
-
result |= 8 /* Geometry */;
|
|
4727
|
-
}
|
|
4728
4774
|
if (dTop > 0 && dBottom > 0)
|
|
4729
4775
|
bias = Math.max(dTop, dBottom);
|
|
4730
4776
|
else if (dTop < 0 && dBottom < 0)
|
|
@@ -4765,8 +4811,9 @@ class ViewState {
|
|
|
4765
4811
|
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);
|
|
4766
4812
|
// If scrollTarget is given, make sure the viewport includes that position
|
|
4767
4813
|
if (scrollTarget) {
|
|
4768
|
-
let { head } = scrollTarget.range
|
|
4814
|
+
let { head } = scrollTarget.range;
|
|
4769
4815
|
if (head < viewport.from || head > viewport.to) {
|
|
4816
|
+
let viewHeight = Math.min(this.editorHeight, this.pixelViewport.bottom - this.pixelViewport.top);
|
|
4770
4817
|
let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
|
|
4771
4818
|
if (scrollTarget.y == "center")
|
|
4772
4819
|
topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
|
|
@@ -5566,9 +5613,8 @@ function applyDOMChange(view, start, end, typeOver) {
|
|
|
5566
5613
|
return;
|
|
5567
5614
|
let { from, to } = bounds;
|
|
5568
5615
|
let selPoints = view.docView.impreciseHead || view.docView.impreciseAnchor ? [] : selectionPoints(view);
|
|
5569
|
-
let reader = new DOMReader(selPoints, view);
|
|
5616
|
+
let reader = new DOMReader(selPoints, view.state);
|
|
5570
5617
|
reader.readRange(bounds.startDOM, bounds.endDOM);
|
|
5571
|
-
newSel = selectionFromPoints(selPoints, from);
|
|
5572
5618
|
let preferredPos = sel.from, preferredSide = null;
|
|
5573
5619
|
// Prefer anchoring to end when Backspace is pressed (or, on
|
|
5574
5620
|
// Android, when something was deleted)
|
|
@@ -5577,10 +5623,29 @@ function applyDOMChange(view, start, end, typeOver) {
|
|
|
5577
5623
|
preferredPos = sel.to;
|
|
5578
5624
|
preferredSide = "end";
|
|
5579
5625
|
}
|
|
5580
|
-
let diff = findDiff(view.state.
|
|
5581
|
-
if (diff)
|
|
5626
|
+
let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), reader.text, preferredPos - from, preferredSide);
|
|
5627
|
+
if (diff) {
|
|
5628
|
+
let orig = diff;
|
|
5629
|
+
// Chrome inserts two newlines when pressing shift-enter at the
|
|
5630
|
+
// end of a line. This drops one of those.
|
|
5631
|
+
if (browser.chrome && view.inputState.lastKeyCode == 13 &&
|
|
5632
|
+
diff.toB == diff.from + 2 && reader.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
|
|
5633
|
+
diff.toB--;
|
|
5634
|
+
// Strip leading and trailing zero-width spaces from the inserted
|
|
5635
|
+
// content, to work around widget buffers being moved into text
|
|
5636
|
+
// nodes by the browser.
|
|
5637
|
+
while (diff.from < diff.toB && reader.text[diff.from] == "\u200b") {
|
|
5638
|
+
diff = { from: diff.from + 1, toA: diff.toA, toB: diff.toB };
|
|
5639
|
+
selPoints.forEach(p => p.pos -= p.pos > orig.from ? 1 : 0);
|
|
5640
|
+
}
|
|
5641
|
+
while (diff.toB > diff.from && reader.text[diff.toB - 1] == "\u200b") {
|
|
5642
|
+
diff = { from: diff.from, toA: diff.toA, toB: diff.toB - 1 };
|
|
5643
|
+
selPoints.forEach(p => p.pos -= p.pos > orig.toB ? 1 : 0);
|
|
5644
|
+
}
|
|
5582
5645
|
change = { from: from + diff.from, to: from + diff.toA,
|
|
5583
|
-
insert:
|
|
5646
|
+
insert: state.Text.of(reader.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
|
|
5647
|
+
}
|
|
5648
|
+
newSel = selectionFromPoints(selPoints, from);
|
|
5584
5649
|
}
|
|
5585
5650
|
else if (view.hasFocus || !view.state.facet(editable)) {
|
|
5586
5651
|
let domSel = view.observer.selectionRange;
|
|
@@ -5637,19 +5702,47 @@ function applyDOMChange(view, start, end, typeOver) {
|
|
|
5637
5702
|
view.inputState.composing++;
|
|
5638
5703
|
let tr;
|
|
5639
5704
|
if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
|
|
5640
|
-
(!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length)
|
|
5705
|
+
(!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
|
|
5706
|
+
view.inputState.composing < 0) {
|
|
5641
5707
|
let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
|
|
5642
5708
|
let after = sel.to > change.to ? startState.sliceDoc(change.to, sel.to) : "";
|
|
5643
|
-
tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) +
|
|
5644
|
-
after));
|
|
5709
|
+
tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
|
|
5645
5710
|
}
|
|
5646
5711
|
else {
|
|
5647
5712
|
let changes = startState.changes(change);
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5713
|
+
let mainSel = newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
|
|
5714
|
+
? newSel.main : undefined;
|
|
5715
|
+
// Try to apply a composition change to all cursors
|
|
5716
|
+
if (startState.selection.ranges.length > 1 && view.inputState.composing >= 0 &&
|
|
5717
|
+
change.to <= sel.to && change.to >= sel.to - 10) {
|
|
5718
|
+
let replaced = view.state.sliceDoc(change.from, change.to);
|
|
5719
|
+
let compositionRange = compositionSurroundingNode(view) || view.state.doc.lineAt(sel.head);
|
|
5720
|
+
let offset = sel.to - change.to, size = sel.to - sel.from;
|
|
5721
|
+
tr = startState.changeByRange(range => {
|
|
5722
|
+
if (range.from == sel.from && range.to == sel.to)
|
|
5723
|
+
return { changes, range: mainSel || range.map(changes) };
|
|
5724
|
+
let to = range.to - offset, from = to - replaced.length;
|
|
5725
|
+
if (range.to - range.from != size || view.state.sliceDoc(from, to) != replaced ||
|
|
5726
|
+
// Unfortunately, there's no way to make multiple
|
|
5727
|
+
// changes in the same node work without aborting
|
|
5728
|
+
// composition, so cursors in the composition range are
|
|
5729
|
+
// ignored.
|
|
5730
|
+
compositionRange && range.to >= compositionRange.from && range.from <= compositionRange.to)
|
|
5731
|
+
return { range };
|
|
5732
|
+
let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
|
|
5733
|
+
return {
|
|
5734
|
+
changes: rangeChanges,
|
|
5735
|
+
range: !mainSel ? range.map(rangeChanges) :
|
|
5736
|
+
state.EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
|
|
5737
|
+
};
|
|
5738
|
+
});
|
|
5739
|
+
}
|
|
5740
|
+
else {
|
|
5741
|
+
tr = {
|
|
5742
|
+
changes,
|
|
5743
|
+
selection: mainSel && startState.selection.replaceRange(mainSel)
|
|
5744
|
+
};
|
|
5745
|
+
}
|
|
5653
5746
|
}
|
|
5654
5747
|
let userEvent = "input.type";
|
|
5655
5748
|
if (view.composing) {
|
|
@@ -5920,6 +6013,7 @@ class EditorView {
|
|
|
5920
6013
|
return;
|
|
5921
6014
|
}
|
|
5922
6015
|
this.updateState = 2 /* Updating */;
|
|
6016
|
+
let hadFocus = this.hasFocus;
|
|
5923
6017
|
try {
|
|
5924
6018
|
for (let plugin of this.plugins)
|
|
5925
6019
|
plugin.destroy(this);
|
|
@@ -5937,6 +6031,8 @@ class EditorView {
|
|
|
5937
6031
|
finally {
|
|
5938
6032
|
this.updateState = 0 /* Idle */;
|
|
5939
6033
|
}
|
|
6034
|
+
if (hadFocus)
|
|
6035
|
+
this.focus();
|
|
5940
6036
|
this.requestMeasure();
|
|
5941
6037
|
}
|
|
5942
6038
|
updatePlugins(update) {
|
|
@@ -6006,7 +6102,7 @@ class EditorView {
|
|
|
6006
6102
|
return BadMeasure;
|
|
6007
6103
|
}
|
|
6008
6104
|
});
|
|
6009
|
-
let update = new ViewUpdate(this, this.state), redrawn = false;
|
|
6105
|
+
let update = new ViewUpdate(this, this.state), redrawn = false, scrolled = false;
|
|
6010
6106
|
update.flags |= changed;
|
|
6011
6107
|
if (!updated)
|
|
6012
6108
|
updated = update;
|
|
@@ -6033,11 +6129,12 @@ class EditorView {
|
|
|
6033
6129
|
if (this.viewState.scrollTarget) {
|
|
6034
6130
|
this.docView.scrollIntoView(this.viewState.scrollTarget);
|
|
6035
6131
|
this.viewState.scrollTarget = null;
|
|
6132
|
+
scrolled = true;
|
|
6036
6133
|
}
|
|
6037
6134
|
if (redrawn)
|
|
6038
6135
|
this.docView.updateSelection(true);
|
|
6039
6136
|
if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
|
|
6040
|
-
this.measureRequests.length == 0)
|
|
6137
|
+
!scrolled && this.measureRequests.length == 0)
|
|
6041
6138
|
break;
|
|
6042
6139
|
}
|
|
6043
6140
|
}
|
|
@@ -6173,7 +6270,7 @@ class EditorView {
|
|
|
6173
6270
|
(`view.contentDOM.getBoundingClientRect().top`) to limit layout
|
|
6174
6271
|
queries.
|
|
6175
6272
|
|
|
6176
|
-
*Deprecated: use `
|
|
6273
|
+
*Deprecated: use `elementAtHeight` instead.*
|
|
6177
6274
|
*/
|
|
6178
6275
|
blockAtHeight(height, docTop) {
|
|
6179
6276
|
let top = ensureTop(docTop, this);
|
|
@@ -6585,6 +6682,13 @@ mechanism for providing decorations.
|
|
|
6585
6682
|
*/
|
|
6586
6683
|
EditorView.decorations = decorations;
|
|
6587
6684
|
/**
|
|
6685
|
+
This facet records whether a dark theme is active. The extension
|
|
6686
|
+
returned by [`theme`](https://codemirror.net/6/docs/ref/#view.EditorView^theme) automatically
|
|
6687
|
+
includes an instance of this when the `dark` option is set to
|
|
6688
|
+
true.
|
|
6689
|
+
*/
|
|
6690
|
+
EditorView.darkTheme = darkTheme;
|
|
6691
|
+
/**
|
|
6588
6692
|
Facet that provides additional DOM attributes for the editor's
|
|
6589
6693
|
editable DOM element.
|
|
6590
6694
|
*/
|
|
@@ -7011,7 +7115,7 @@ function measureRange(view, range) {
|
|
|
7011
7115
|
return pieces(top).concat(between).concat(pieces(bottom));
|
|
7012
7116
|
}
|
|
7013
7117
|
function piece(left, top, right, bottom) {
|
|
7014
|
-
return new Piece(left - base.left, top - base.top
|
|
7118
|
+
return new Piece(left - base.left, top - base.top - 0.01 /* Epsilon */, right - left, bottom - top + 0.01 /* Epsilon */, "cm-selectionBackground");
|
|
7015
7119
|
}
|
|
7016
7120
|
function pieces({ top, bottom, horizontal }) {
|
|
7017
7121
|
let pieces = [];
|
package/dist/index.d.ts
CHANGED
|
@@ -802,7 +802,7 @@ declare class EditorView {
|
|
|
802
802
|
(`view.contentDOM.getBoundingClientRect().top`) to limit layout
|
|
803
803
|
queries.
|
|
804
804
|
|
|
805
|
-
*Deprecated: use `
|
|
805
|
+
*Deprecated: use `elementAtHeight` instead.*
|
|
806
806
|
*/
|
|
807
807
|
blockAtHeight(height: number, docTop?: number): BlockInfo;
|
|
808
808
|
/**
|
|
@@ -1156,6 +1156,13 @@ declare class EditorView {
|
|
|
1156
1156
|
dark?: boolean;
|
|
1157
1157
|
}): Extension;
|
|
1158
1158
|
/**
|
|
1159
|
+
This facet records whether a dark theme is active. The extension
|
|
1160
|
+
returned by [`theme`](https://codemirror.net/6/docs/ref/#view.EditorView^theme) automatically
|
|
1161
|
+
includes an instance of this when the `dark` option is set to
|
|
1162
|
+
true.
|
|
1163
|
+
*/
|
|
1164
|
+
static darkTheme: Facet<boolean, boolean>;
|
|
1165
|
+
/**
|
|
1159
1166
|
Create an extension that adds styles to the base theme. Like
|
|
1160
1167
|
with [`theme`](https://codemirror.net/6/docs/ref/#view.EditorView^theme), use `&` to indicate the
|
|
1161
1168
|
place of the editor wrapper element when directly targeting
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MapMode, Text as Text$1, Facet, StateEffect, ChangeSet, EditorSelection,
|
|
1
|
+
import { MapMode, Text as Text$1, Facet, StateEffect, ChangeSet, EditorSelection, EditorState, CharCategory, 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';
|
|
@@ -599,9 +599,8 @@ function mergeChildrenInto(parent, from, to, insert, openStart, openEnd) {
|
|
|
599
599
|
replaceRange(parent, fromI, fromOff, toI, toOff, insert, 0, openStart, openEnd);
|
|
600
600
|
}
|
|
601
601
|
|
|
602
|
-
let
|
|
603
|
-
|
|
604
|
-
: [{ userAgent: "", vendor: "", platform: "" }, { documentElement: { style: {} } }];
|
|
602
|
+
let nav = typeof navigator != "undefined" ? navigator : { userAgent: "", vendor: "", platform: "" };
|
|
603
|
+
let doc = typeof document != "undefined" ? document : { documentElement: { style: {} } };
|
|
605
604
|
const ie_edge = /*@__PURE__*//Edge\/(\d+)/.exec(nav.userAgent);
|
|
606
605
|
const ie_upto10 = /*@__PURE__*//MSIE \d/.test(nav.userAgent);
|
|
607
606
|
const ie_11up = /*@__PURE__*//Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(nav.userAgent);
|
|
@@ -1127,8 +1126,8 @@ class Decoration extends RangeValue {
|
|
|
1127
1126
|
static replace(spec) {
|
|
1128
1127
|
let block = !!spec.block;
|
|
1129
1128
|
let { start, end } = getInclusive(spec, block);
|
|
1130
|
-
let startSide =
|
|
1131
|
-
let endSide =
|
|
1129
|
+
let startSide = (start ? (block ? -300000000 /* BlockIncStart */ : -1 /* InlineIncStart */) : 400000000 /* NonIncStart */) - 1;
|
|
1130
|
+
let endSide = (end ? (block ? 200000000 /* BlockIncEnd */ : 1 /* InlineIncEnd */) : -500000000 /* NonIncEnd */) + 1;
|
|
1132
1131
|
return new PointDecoration(spec, startSide, endSide, block, spec.widget || null, true);
|
|
1133
1132
|
}
|
|
1134
1133
|
/**
|
|
@@ -1233,7 +1232,7 @@ function widgetsEq(a, b) {
|
|
|
1233
1232
|
}
|
|
1234
1233
|
function addRange(from, to, ranges, margin = 0) {
|
|
1235
1234
|
let last = ranges.length - 1;
|
|
1236
|
-
if (last >= 0 && ranges[last] + margin
|
|
1235
|
+
if (last >= 0 && ranges[last] + margin >= from)
|
|
1237
1236
|
ranges[last] = Math.max(ranges[last], to);
|
|
1238
1237
|
else
|
|
1239
1238
|
ranges.push(from, to);
|
|
@@ -1514,7 +1513,7 @@ class ContentBuilder {
|
|
|
1514
1513
|
}
|
|
1515
1514
|
}
|
|
1516
1515
|
let take = Math.min(this.text.length - this.textOff, length, 512 /* Chunk */);
|
|
1517
|
-
this.flushBuffer(active);
|
|
1516
|
+
this.flushBuffer(active.slice(0, openStart));
|
|
1518
1517
|
this.getLine().append(wrapMarks(new TextView(this.text.slice(this.textOff, this.textOff + take)), active), openStart);
|
|
1519
1518
|
this.atCursorPos = true;
|
|
1520
1519
|
this.textOff += take;
|
|
@@ -1573,11 +1572,13 @@ class ContentBuilder {
|
|
|
1573
1572
|
this.openStart = openStart;
|
|
1574
1573
|
}
|
|
1575
1574
|
filterPoint(from, to, value, index) {
|
|
1576
|
-
if (index
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1575
|
+
if (index < this.disallowBlockEffectsBelow && value instanceof PointDecoration) {
|
|
1576
|
+
if (value.block)
|
|
1577
|
+
throw new RangeError("Block decorations may not be specified via plugins");
|
|
1578
|
+
if (to > this.doc.lineAt(this.pos).to)
|
|
1579
|
+
throw new RangeError("Decorations that replace line breaks may not be specified via plugins");
|
|
1580
|
+
}
|
|
1581
|
+
return true;
|
|
1581
1582
|
}
|
|
1582
1583
|
static build(text, from, to, decorations, pluginDecorationLength) {
|
|
1583
1584
|
let builder = new ContentBuilder(text, from, to, pluginDecorationLength);
|
|
@@ -2300,12 +2301,18 @@ function moveVisually(line, order, dir, start, forward) {
|
|
|
2300
2301
|
return EditorSelection.cursor(nextIndex + line.from, forward ? -1 : 1, span.level);
|
|
2301
2302
|
}
|
|
2302
2303
|
|
|
2304
|
+
const LineBreakPlaceholder = "\uffff";
|
|
2303
2305
|
class DOMReader {
|
|
2304
|
-
constructor(points,
|
|
2306
|
+
constructor(points, state) {
|
|
2305
2307
|
this.points = points;
|
|
2306
|
-
this.view = view;
|
|
2307
2308
|
this.text = "";
|
|
2308
|
-
this.
|
|
2309
|
+
this.lineSeparator = state.facet(EditorState.lineSeparator);
|
|
2310
|
+
}
|
|
2311
|
+
append(text) {
|
|
2312
|
+
this.text += text;
|
|
2313
|
+
}
|
|
2314
|
+
lineBreak() {
|
|
2315
|
+
this.text += LineBreakPlaceholder;
|
|
2309
2316
|
}
|
|
2310
2317
|
readRange(start, end) {
|
|
2311
2318
|
if (!start)
|
|
@@ -2321,33 +2328,61 @@ class DOMReader {
|
|
|
2321
2328
|
if (view && nextView ? view.breakAfter :
|
|
2322
2329
|
(view ? view.breakAfter : isBlockElement(cur)) ||
|
|
2323
2330
|
(isBlockElement(next) && (cur.nodeName != "BR" || cur.cmIgnore)))
|
|
2324
|
-
this.
|
|
2331
|
+
this.lineBreak();
|
|
2325
2332
|
cur = next;
|
|
2326
2333
|
}
|
|
2327
2334
|
this.findPointBefore(parent, end);
|
|
2328
2335
|
return this;
|
|
2329
2336
|
}
|
|
2337
|
+
readTextNode(node) {
|
|
2338
|
+
let text = node.nodeValue;
|
|
2339
|
+
for (let point of this.points)
|
|
2340
|
+
if (point.node == node)
|
|
2341
|
+
point.pos = this.text.length + Math.min(point.offset, text.length);
|
|
2342
|
+
for (let off = 0, re = this.lineSeparator ? null : /\r\n?|\n/g;;) {
|
|
2343
|
+
let nextBreak = -1, breakSize = 1, m;
|
|
2344
|
+
if (this.lineSeparator) {
|
|
2345
|
+
nextBreak = text.indexOf(this.lineSeparator, off);
|
|
2346
|
+
breakSize = this.lineSeparator.length;
|
|
2347
|
+
}
|
|
2348
|
+
else if (m = re.exec(text)) {
|
|
2349
|
+
nextBreak = m.index;
|
|
2350
|
+
breakSize = m[0].length;
|
|
2351
|
+
}
|
|
2352
|
+
this.append(text.slice(off, nextBreak < 0 ? text.length : nextBreak));
|
|
2353
|
+
if (nextBreak < 0)
|
|
2354
|
+
break;
|
|
2355
|
+
this.lineBreak();
|
|
2356
|
+
if (breakSize > 1)
|
|
2357
|
+
for (let point of this.points)
|
|
2358
|
+
if (point.node == node && point.pos > this.text.length)
|
|
2359
|
+
point.pos -= breakSize - 1;
|
|
2360
|
+
off = nextBreak + breakSize;
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2330
2363
|
readNode(node) {
|
|
2331
2364
|
if (node.cmIgnore)
|
|
2332
2365
|
return;
|
|
2333
2366
|
let view = ContentView.get(node);
|
|
2334
2367
|
let fromView = view && view.overrideDOMText;
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2368
|
+
if (fromView != null) {
|
|
2369
|
+
this.findPointInside(node, fromView.length);
|
|
2370
|
+
for (let i = fromView.iter(); !i.next().done;) {
|
|
2371
|
+
if (i.lineBreak)
|
|
2372
|
+
this.lineBreak();
|
|
2373
|
+
else
|
|
2374
|
+
this.append(i.value);
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
else if (node.nodeType == 3) {
|
|
2378
|
+
this.readTextNode(node);
|
|
2379
|
+
}
|
|
2380
|
+
else if (node.nodeName == "BR") {
|
|
2381
|
+
if (node.nextSibling)
|
|
2382
|
+
this.lineBreak();
|
|
2383
|
+
}
|
|
2384
|
+
else if (node.nodeType == 1) {
|
|
2343
2385
|
this.readRange(node.firstChild, null);
|
|
2344
|
-
if (text != null) {
|
|
2345
|
-
this.findPointIn(node, text.length);
|
|
2346
|
-
this.text += text;
|
|
2347
|
-
// Chrome inserts two newlines when pressing shift-enter at the
|
|
2348
|
-
// end of a line. This drops one of those.
|
|
2349
|
-
if (browser.chrome && this.view.inputState.lastKeyCode == 13 && !node.nextSibling && /\n\n$/.test(this.text))
|
|
2350
|
-
this.text = this.text.slice(0, -1);
|
|
2351
2386
|
}
|
|
2352
2387
|
}
|
|
2353
2388
|
findPointBefore(node, next) {
|
|
@@ -2355,10 +2390,10 @@ class DOMReader {
|
|
|
2355
2390
|
if (point.node == node && node.childNodes[point.offset] == next)
|
|
2356
2391
|
point.pos = this.text.length;
|
|
2357
2392
|
}
|
|
2358
|
-
|
|
2393
|
+
findPointInside(node, maxLen) {
|
|
2359
2394
|
for (let point of this.points)
|
|
2360
|
-
if (point.node == node)
|
|
2361
|
-
point.pos = this.text.length + Math.min(point.offset
|
|
2395
|
+
if (node.nodeType == 3 ? point.node == node : node.contains(point.node))
|
|
2396
|
+
point.pos = this.text.length + Math.min(maxLen, point.offset);
|
|
2362
2397
|
}
|
|
2363
2398
|
}
|
|
2364
2399
|
function isBlockElement(node) {
|
|
@@ -2763,51 +2798,57 @@ class BlockGapWidget extends WidgetType {
|
|
|
2763
2798
|
}
|
|
2764
2799
|
get estimatedHeight() { return this.height; }
|
|
2765
2800
|
}
|
|
2766
|
-
function
|
|
2801
|
+
function compositionSurroundingNode(view) {
|
|
2767
2802
|
let sel = view.observer.selectionRange;
|
|
2768
2803
|
let textNode = sel.focusNode && nearbyTextNode(sel.focusNode, sel.focusOffset, 0);
|
|
2769
2804
|
if (!textNode)
|
|
2770
|
-
return
|
|
2805
|
+
return null;
|
|
2771
2806
|
let cView = view.docView.nearest(textNode);
|
|
2772
2807
|
if (!cView)
|
|
2773
|
-
return
|
|
2774
|
-
let from, to, topNode = textNode;
|
|
2808
|
+
return null;
|
|
2775
2809
|
if (cView instanceof LineView) {
|
|
2810
|
+
let topNode = textNode;
|
|
2776
2811
|
while (topNode.parentNode != cView.dom)
|
|
2777
2812
|
topNode = topNode.parentNode;
|
|
2778
2813
|
let prev = topNode.previousSibling;
|
|
2779
2814
|
while (prev && !ContentView.get(prev))
|
|
2780
2815
|
prev = prev.previousSibling;
|
|
2781
|
-
|
|
2816
|
+
let pos = prev ? ContentView.get(prev).posAtEnd : cView.posAtStart;
|
|
2817
|
+
return { from: pos, to: pos, node: topNode, text: textNode };
|
|
2782
2818
|
}
|
|
2783
2819
|
else {
|
|
2784
2820
|
for (;;) {
|
|
2785
2821
|
let { parent } = cView;
|
|
2786
2822
|
if (!parent)
|
|
2787
|
-
return
|
|
2823
|
+
return null;
|
|
2788
2824
|
if (parent instanceof LineView)
|
|
2789
2825
|
break;
|
|
2790
2826
|
cView = parent;
|
|
2791
2827
|
}
|
|
2792
|
-
from = cView.posAtStart;
|
|
2793
|
-
|
|
2794
|
-
topNode = cView.dom;
|
|
2828
|
+
let from = cView.posAtStart;
|
|
2829
|
+
return { from, to: from + cView.length, node: cView.dom, text: textNode };
|
|
2795
2830
|
}
|
|
2831
|
+
}
|
|
2832
|
+
function computeCompositionDeco(view, changes) {
|
|
2833
|
+
let surrounding = compositionSurroundingNode(view);
|
|
2834
|
+
if (!surrounding)
|
|
2835
|
+
return Decoration.none;
|
|
2836
|
+
let { from, to, node, text: textNode } = surrounding;
|
|
2796
2837
|
let newFrom = changes.mapPos(from, 1), newTo = Math.max(newFrom, changes.mapPos(to, -1));
|
|
2797
|
-
let { state } = view, text =
|
|
2798
|
-
new DOMReader([],
|
|
2838
|
+
let { state } = view, text = node.nodeType == 3 ? node.nodeValue :
|
|
2839
|
+
new DOMReader([], state).readRange(node.firstChild, null).text;
|
|
2799
2840
|
if (newTo - newFrom < text.length) {
|
|
2800
|
-
if (state.
|
|
2841
|
+
if (state.doc.sliceString(newFrom, Math.min(state.doc.length, newFrom + text.length), LineBreakPlaceholder) == text)
|
|
2801
2842
|
newTo = newFrom + text.length;
|
|
2802
|
-
else if (state.
|
|
2843
|
+
else if (state.doc.sliceString(Math.max(0, newTo - text.length), newTo, LineBreakPlaceholder) == text)
|
|
2803
2844
|
newFrom = newTo - text.length;
|
|
2804
2845
|
else
|
|
2805
2846
|
return Decoration.none;
|
|
2806
2847
|
}
|
|
2807
|
-
else if (state.
|
|
2848
|
+
else if (state.doc.sliceString(newFrom, newTo, LineBreakPlaceholder) != text) {
|
|
2808
2849
|
return Decoration.none;
|
|
2809
2850
|
}
|
|
2810
|
-
return Decoration.set(Decoration.replace({ widget: new CompositionWidget(
|
|
2851
|
+
return Decoration.set(Decoration.replace({ widget: new CompositionWidget(node, textNode) }).range(newFrom, newTo));
|
|
2811
2852
|
}
|
|
2812
2853
|
class CompositionWidget extends WidgetType {
|
|
2813
2854
|
constructor(top, text) {
|
|
@@ -3144,7 +3185,7 @@ function byGroup(view, pos, start) {
|
|
|
3144
3185
|
function moveVertically(view, start, forward, distance) {
|
|
3145
3186
|
let startPos = start.head, dir = forward ? 1 : -1;
|
|
3146
3187
|
if (startPos == (forward ? view.state.doc.length : 0))
|
|
3147
|
-
return EditorSelection.cursor(startPos);
|
|
3188
|
+
return EditorSelection.cursor(startPos, start.assoc);
|
|
3148
3189
|
let goal = start.goalColumn, startY;
|
|
3149
3190
|
let rect = view.contentDOM.getBoundingClientRect();
|
|
3150
3191
|
let startCoords = view.coordsAtPos(startPos), docTop = view.documentTop;
|
|
@@ -3165,7 +3206,7 @@ function moveVertically(view, start, forward, distance) {
|
|
|
3165
3206
|
let curY = startY + (dist + extra) * dir;
|
|
3166
3207
|
let pos = posAtCoords(view, { x: resolvedGoal, y: curY }, false, dir);
|
|
3167
3208
|
if (curY < rect.top || curY > rect.bottom || (dir < 0 ? pos < startPos : pos > startPos))
|
|
3168
|
-
return EditorSelection.cursor(pos,
|
|
3209
|
+
return EditorSelection.cursor(pos, start.assoc, undefined, goal);
|
|
3169
3210
|
}
|
|
3170
3211
|
}
|
|
3171
3212
|
function skipAtoms(view, oldPos, pos) {
|
|
@@ -4593,6 +4634,7 @@ class ViewState {
|
|
|
4593
4634
|
this.contentDOMWidth = 0;
|
|
4594
4635
|
this.contentDOMHeight = 0;
|
|
4595
4636
|
this.editorHeight = 0;
|
|
4637
|
+
this.editorWidth = 0;
|
|
4596
4638
|
this.heightOracle = new HeightOracle;
|
|
4597
4639
|
// See VP.MaxDOMHeight
|
|
4598
4640
|
this.scaler = IdScaler;
|
|
@@ -4675,6 +4717,12 @@ class ViewState {
|
|
|
4675
4717
|
let refresh = this.heightOracle.mustRefreshForStyle(whiteSpace, direction);
|
|
4676
4718
|
let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != dom.clientHeight;
|
|
4677
4719
|
let result = 0, bias = 0;
|
|
4720
|
+
if (this.editorWidth != view.scrollDOM.clientWidth) {
|
|
4721
|
+
if (oracle.lineWrapping)
|
|
4722
|
+
measureContent = true;
|
|
4723
|
+
this.editorWidth = view.scrollDOM.clientWidth;
|
|
4724
|
+
result |= 8 /* Geometry */;
|
|
4725
|
+
}
|
|
4678
4726
|
if (measureContent) {
|
|
4679
4727
|
this.mustMeasureContent = false;
|
|
4680
4728
|
this.contentDOMHeight = dom.clientHeight;
|
|
@@ -4687,7 +4735,8 @@ class ViewState {
|
|
|
4687
4735
|
}
|
|
4688
4736
|
}
|
|
4689
4737
|
// Pixel viewport
|
|
4690
|
-
let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 }
|
|
4738
|
+
let pixelViewport = this.printing ? { top: -1e8, bottom: 1e8, left: -1e8, right: 1e8 }
|
|
4739
|
+
: visiblePixelRange(dom, this.paddingTop);
|
|
4691
4740
|
let dTop = pixelViewport.top - this.pixelViewport.top, dBottom = pixelViewport.bottom - this.pixelViewport.bottom;
|
|
4692
4741
|
this.pixelViewport = pixelViewport;
|
|
4693
4742
|
let inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
|
|
@@ -4698,11 +4747,16 @@ class ViewState {
|
|
|
4698
4747
|
}
|
|
4699
4748
|
if (!this.inView)
|
|
4700
4749
|
return 0;
|
|
4750
|
+
let contentWidth = dom.clientWidth;
|
|
4751
|
+
if (this.contentDOMWidth != contentWidth || this.editorHeight != view.scrollDOM.clientHeight) {
|
|
4752
|
+
this.contentDOMWidth = contentWidth;
|
|
4753
|
+
this.editorHeight = view.scrollDOM.clientHeight;
|
|
4754
|
+
result |= 8 /* Geometry */;
|
|
4755
|
+
}
|
|
4701
4756
|
if (measureContent) {
|
|
4702
4757
|
let lineHeights = view.docView.measureVisibleLineHeights();
|
|
4703
4758
|
if (oracle.mustRefreshForHeights(lineHeights))
|
|
4704
4759
|
refresh = true;
|
|
4705
|
-
let contentWidth = dom.clientWidth;
|
|
4706
4760
|
if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
|
|
4707
4761
|
let { lineHeight, charWidth } = view.docView.measureTextSize();
|
|
4708
4762
|
refresh = oracle.refresh(whiteSpace, direction, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
|
|
@@ -4711,14 +4765,6 @@ class ViewState {
|
|
|
4711
4765
|
result |= 8 /* Geometry */;
|
|
4712
4766
|
}
|
|
4713
4767
|
}
|
|
4714
|
-
if (this.contentDOMWidth != contentWidth) {
|
|
4715
|
-
this.contentDOMWidth = contentWidth;
|
|
4716
|
-
result |= 8 /* Geometry */;
|
|
4717
|
-
}
|
|
4718
|
-
if (this.editorHeight != view.scrollDOM.clientHeight) {
|
|
4719
|
-
this.editorHeight = view.scrollDOM.clientHeight;
|
|
4720
|
-
result |= 8 /* Geometry */;
|
|
4721
|
-
}
|
|
4722
4768
|
if (dTop > 0 && dBottom > 0)
|
|
4723
4769
|
bias = Math.max(dTop, dBottom);
|
|
4724
4770
|
else if (dTop < 0 && dBottom < 0)
|
|
@@ -4759,8 +4805,9 @@ class ViewState {
|
|
|
4759
4805
|
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);
|
|
4760
4806
|
// If scrollTarget is given, make sure the viewport includes that position
|
|
4761
4807
|
if (scrollTarget) {
|
|
4762
|
-
let { head } = scrollTarget.range
|
|
4808
|
+
let { head } = scrollTarget.range;
|
|
4763
4809
|
if (head < viewport.from || head > viewport.to) {
|
|
4810
|
+
let viewHeight = Math.min(this.editorHeight, this.pixelViewport.bottom - this.pixelViewport.top);
|
|
4764
4811
|
let block = map.lineAt(head, QueryType.ByPos, doc, 0, 0), topPos;
|
|
4765
4812
|
if (scrollTarget.y == "center")
|
|
4766
4813
|
topPos = (block.top + block.bottom) / 2 - viewHeight / 2;
|
|
@@ -5560,9 +5607,8 @@ function applyDOMChange(view, start, end, typeOver) {
|
|
|
5560
5607
|
return;
|
|
5561
5608
|
let { from, to } = bounds;
|
|
5562
5609
|
let selPoints = view.docView.impreciseHead || view.docView.impreciseAnchor ? [] : selectionPoints(view);
|
|
5563
|
-
let reader = new DOMReader(selPoints, view);
|
|
5610
|
+
let reader = new DOMReader(selPoints, view.state);
|
|
5564
5611
|
reader.readRange(bounds.startDOM, bounds.endDOM);
|
|
5565
|
-
newSel = selectionFromPoints(selPoints, from);
|
|
5566
5612
|
let preferredPos = sel.from, preferredSide = null;
|
|
5567
5613
|
// Prefer anchoring to end when Backspace is pressed (or, on
|
|
5568
5614
|
// Android, when something was deleted)
|
|
@@ -5571,10 +5617,29 @@ function applyDOMChange(view, start, end, typeOver) {
|
|
|
5571
5617
|
preferredPos = sel.to;
|
|
5572
5618
|
preferredSide = "end";
|
|
5573
5619
|
}
|
|
5574
|
-
let diff = findDiff(view.state.
|
|
5575
|
-
if (diff)
|
|
5620
|
+
let diff = findDiff(view.state.doc.sliceString(from, to, LineBreakPlaceholder), reader.text, preferredPos - from, preferredSide);
|
|
5621
|
+
if (diff) {
|
|
5622
|
+
let orig = diff;
|
|
5623
|
+
// Chrome inserts two newlines when pressing shift-enter at the
|
|
5624
|
+
// end of a line. This drops one of those.
|
|
5625
|
+
if (browser.chrome && view.inputState.lastKeyCode == 13 &&
|
|
5626
|
+
diff.toB == diff.from + 2 && reader.text.slice(diff.from, diff.toB) == LineBreakPlaceholder + LineBreakPlaceholder)
|
|
5627
|
+
diff.toB--;
|
|
5628
|
+
// Strip leading and trailing zero-width spaces from the inserted
|
|
5629
|
+
// content, to work around widget buffers being moved into text
|
|
5630
|
+
// nodes by the browser.
|
|
5631
|
+
while (diff.from < diff.toB && reader.text[diff.from] == "\u200b") {
|
|
5632
|
+
diff = { from: diff.from + 1, toA: diff.toA, toB: diff.toB };
|
|
5633
|
+
selPoints.forEach(p => p.pos -= p.pos > orig.from ? 1 : 0);
|
|
5634
|
+
}
|
|
5635
|
+
while (diff.toB > diff.from && reader.text[diff.toB - 1] == "\u200b") {
|
|
5636
|
+
diff = { from: diff.from, toA: diff.toA, toB: diff.toB - 1 };
|
|
5637
|
+
selPoints.forEach(p => p.pos -= p.pos > orig.toB ? 1 : 0);
|
|
5638
|
+
}
|
|
5576
5639
|
change = { from: from + diff.from, to: from + diff.toA,
|
|
5577
|
-
insert:
|
|
5640
|
+
insert: Text$1.of(reader.text.slice(diff.from, diff.toB).split(LineBreakPlaceholder)) };
|
|
5641
|
+
}
|
|
5642
|
+
newSel = selectionFromPoints(selPoints, from);
|
|
5578
5643
|
}
|
|
5579
5644
|
else if (view.hasFocus || !view.state.facet(editable)) {
|
|
5580
5645
|
let domSel = view.observer.selectionRange;
|
|
@@ -5631,19 +5696,47 @@ function applyDOMChange(view, start, end, typeOver) {
|
|
|
5631
5696
|
view.inputState.composing++;
|
|
5632
5697
|
let tr;
|
|
5633
5698
|
if (change.from >= sel.from && change.to <= sel.to && change.to - change.from >= (sel.to - sel.from) / 3 &&
|
|
5634
|
-
(!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length)
|
|
5699
|
+
(!newSel || newSel.main.empty && newSel.main.from == change.from + change.insert.length) &&
|
|
5700
|
+
view.inputState.composing < 0) {
|
|
5635
5701
|
let before = sel.from < change.from ? startState.sliceDoc(sel.from, change.from) : "";
|
|
5636
5702
|
let after = sel.to > change.to ? startState.sliceDoc(change.to, sel.to) : "";
|
|
5637
|
-
tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) +
|
|
5638
|
-
after));
|
|
5703
|
+
tr = startState.replaceSelection(view.state.toText(before + change.insert.sliceString(0, undefined, view.state.lineBreak) + after));
|
|
5639
5704
|
}
|
|
5640
5705
|
else {
|
|
5641
5706
|
let changes = startState.changes(change);
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5707
|
+
let mainSel = newSel && !startState.selection.main.eq(newSel.main) && newSel.main.to <= changes.newLength
|
|
5708
|
+
? newSel.main : undefined;
|
|
5709
|
+
// Try to apply a composition change to all cursors
|
|
5710
|
+
if (startState.selection.ranges.length > 1 && view.inputState.composing >= 0 &&
|
|
5711
|
+
change.to <= sel.to && change.to >= sel.to - 10) {
|
|
5712
|
+
let replaced = view.state.sliceDoc(change.from, change.to);
|
|
5713
|
+
let compositionRange = compositionSurroundingNode(view) || view.state.doc.lineAt(sel.head);
|
|
5714
|
+
let offset = sel.to - change.to, size = sel.to - sel.from;
|
|
5715
|
+
tr = startState.changeByRange(range => {
|
|
5716
|
+
if (range.from == sel.from && range.to == sel.to)
|
|
5717
|
+
return { changes, range: mainSel || range.map(changes) };
|
|
5718
|
+
let to = range.to - offset, from = to - replaced.length;
|
|
5719
|
+
if (range.to - range.from != size || view.state.sliceDoc(from, to) != replaced ||
|
|
5720
|
+
// Unfortunately, there's no way to make multiple
|
|
5721
|
+
// changes in the same node work without aborting
|
|
5722
|
+
// composition, so cursors in the composition range are
|
|
5723
|
+
// ignored.
|
|
5724
|
+
compositionRange && range.to >= compositionRange.from && range.from <= compositionRange.to)
|
|
5725
|
+
return { range };
|
|
5726
|
+
let rangeChanges = startState.changes({ from, to, insert: change.insert }), selOff = range.to - sel.to;
|
|
5727
|
+
return {
|
|
5728
|
+
changes: rangeChanges,
|
|
5729
|
+
range: !mainSel ? range.map(rangeChanges) :
|
|
5730
|
+
EditorSelection.range(Math.max(0, mainSel.anchor + selOff), Math.max(0, mainSel.head + selOff))
|
|
5731
|
+
};
|
|
5732
|
+
});
|
|
5733
|
+
}
|
|
5734
|
+
else {
|
|
5735
|
+
tr = {
|
|
5736
|
+
changes,
|
|
5737
|
+
selection: mainSel && startState.selection.replaceRange(mainSel)
|
|
5738
|
+
};
|
|
5739
|
+
}
|
|
5647
5740
|
}
|
|
5648
5741
|
let userEvent = "input.type";
|
|
5649
5742
|
if (view.composing) {
|
|
@@ -5914,6 +6007,7 @@ class EditorView {
|
|
|
5914
6007
|
return;
|
|
5915
6008
|
}
|
|
5916
6009
|
this.updateState = 2 /* Updating */;
|
|
6010
|
+
let hadFocus = this.hasFocus;
|
|
5917
6011
|
try {
|
|
5918
6012
|
for (let plugin of this.plugins)
|
|
5919
6013
|
plugin.destroy(this);
|
|
@@ -5931,6 +6025,8 @@ class EditorView {
|
|
|
5931
6025
|
finally {
|
|
5932
6026
|
this.updateState = 0 /* Idle */;
|
|
5933
6027
|
}
|
|
6028
|
+
if (hadFocus)
|
|
6029
|
+
this.focus();
|
|
5934
6030
|
this.requestMeasure();
|
|
5935
6031
|
}
|
|
5936
6032
|
updatePlugins(update) {
|
|
@@ -6000,7 +6096,7 @@ class EditorView {
|
|
|
6000
6096
|
return BadMeasure;
|
|
6001
6097
|
}
|
|
6002
6098
|
});
|
|
6003
|
-
let update = new ViewUpdate(this, this.state), redrawn = false;
|
|
6099
|
+
let update = new ViewUpdate(this, this.state), redrawn = false, scrolled = false;
|
|
6004
6100
|
update.flags |= changed;
|
|
6005
6101
|
if (!updated)
|
|
6006
6102
|
updated = update;
|
|
@@ -6027,11 +6123,12 @@ class EditorView {
|
|
|
6027
6123
|
if (this.viewState.scrollTarget) {
|
|
6028
6124
|
this.docView.scrollIntoView(this.viewState.scrollTarget);
|
|
6029
6125
|
this.viewState.scrollTarget = null;
|
|
6126
|
+
scrolled = true;
|
|
6030
6127
|
}
|
|
6031
6128
|
if (redrawn)
|
|
6032
6129
|
this.docView.updateSelection(true);
|
|
6033
6130
|
if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to &&
|
|
6034
|
-
this.measureRequests.length == 0)
|
|
6131
|
+
!scrolled && this.measureRequests.length == 0)
|
|
6035
6132
|
break;
|
|
6036
6133
|
}
|
|
6037
6134
|
}
|
|
@@ -6167,7 +6264,7 @@ class EditorView {
|
|
|
6167
6264
|
(`view.contentDOM.getBoundingClientRect().top`) to limit layout
|
|
6168
6265
|
queries.
|
|
6169
6266
|
|
|
6170
|
-
*Deprecated: use `
|
|
6267
|
+
*Deprecated: use `elementAtHeight` instead.*
|
|
6171
6268
|
*/
|
|
6172
6269
|
blockAtHeight(height, docTop) {
|
|
6173
6270
|
let top = ensureTop(docTop, this);
|
|
@@ -6579,6 +6676,13 @@ mechanism for providing decorations.
|
|
|
6579
6676
|
*/
|
|
6580
6677
|
EditorView.decorations = decorations;
|
|
6581
6678
|
/**
|
|
6679
|
+
This facet records whether a dark theme is active. The extension
|
|
6680
|
+
returned by [`theme`](https://codemirror.net/6/docs/ref/#view.EditorView^theme) automatically
|
|
6681
|
+
includes an instance of this when the `dark` option is set to
|
|
6682
|
+
true.
|
|
6683
|
+
*/
|
|
6684
|
+
EditorView.darkTheme = darkTheme;
|
|
6685
|
+
/**
|
|
6582
6686
|
Facet that provides additional DOM attributes for the editor's
|
|
6583
6687
|
editable DOM element.
|
|
6584
6688
|
*/
|
|
@@ -7005,7 +7109,7 @@ function measureRange(view, range) {
|
|
|
7005
7109
|
return pieces(top).concat(between).concat(pieces(bottom));
|
|
7006
7110
|
}
|
|
7007
7111
|
function piece(left, top, right, bottom) {
|
|
7008
|
-
return new Piece(left - base.left, top - base.top
|
|
7112
|
+
return new Piece(left - base.left, top - base.top - 0.01 /* Epsilon */, right - left, bottom - top + 0.01 /* Epsilon */, "cm-selectionBackground");
|
|
7009
7113
|
}
|
|
7010
7114
|
function pieces({ top, bottom, horizontal }) {
|
|
7011
7115
|
let pieces = [];
|