@codemirror/view 6.39.13 → 6.39.15
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 +18 -0
- package/dist/index.cjs +164 -73
- package/dist/index.js +164 -73
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
## 6.39.15 (2026-02-20)
|
|
2
|
+
|
|
3
|
+
### Bug fixes
|
|
4
|
+
|
|
5
|
+
Fix a regression where the editor would forget previously measured line heights without good reason.
|
|
6
|
+
|
|
7
|
+
Fix an issue where scrolling the cursor into view sometimes wouldn't work on Chrome Android.
|
|
8
|
+
|
|
9
|
+
Fix a bug that broke composition inside of block wrappers.
|
|
10
|
+
|
|
11
|
+
## 6.39.14 (2026-02-12)
|
|
12
|
+
|
|
13
|
+
### Bug fixes
|
|
14
|
+
|
|
15
|
+
Improve performance of `posAtCoords` on long lines.
|
|
16
|
+
|
|
17
|
+
Fix a regression where copy and cut in a shadow DOM on Safari would fall back to the native behavior, often copying the wrong text.
|
|
18
|
+
|
|
1
19
|
## 6.39.13 (2026-02-08)
|
|
2
20
|
|
|
3
21
|
### Bug fixes
|
package/dist/index.cjs
CHANGED
|
@@ -2791,9 +2791,10 @@ class TileUpdate {
|
|
|
2791
2791
|
marks.push(tile);
|
|
2792
2792
|
else if (tile === null || tile === void 0 ? void 0 : tile.isLine())
|
|
2793
2793
|
line = tile;
|
|
2794
|
+
else if (tile instanceof BlockWrapperTile) ; // Ignore
|
|
2794
2795
|
else if (parent.nodeName == "DIV" && !line && parent != this.view.contentDOM)
|
|
2795
2796
|
line = new LineTile(parent, lineBaseAttrs);
|
|
2796
|
-
else
|
|
2797
|
+
else if (!line)
|
|
2797
2798
|
marks.push(MarkTile.of(new MarkDecoration({ tagName: parent.nodeName.toLowerCase(), attributes: getAttrs(parent) }), parent));
|
|
2798
2799
|
}
|
|
2799
2800
|
return { line: line, marks };
|
|
@@ -3419,6 +3420,19 @@ class DocView {
|
|
|
3419
3420
|
};
|
|
3420
3421
|
let { offsetWidth, offsetHeight } = this.view.scrollDOM;
|
|
3421
3422
|
scrollRectIntoView(this.view.scrollDOM, targetRect, range.head < range.anchor ? -1 : 1, target.x, target.y, Math.max(Math.min(target.xMargin, offsetWidth), -offsetWidth), Math.max(Math.min(target.yMargin, offsetHeight), -offsetHeight), this.view.textDirection == exports.Direction.LTR);
|
|
3423
|
+
// On mobile browsers, the visual viewport may be smaller than the
|
|
3424
|
+
// actual reported viewport, causing scrollRectIntoView to fail to
|
|
3425
|
+
// scroll properly. Unfortunately, this visual viewport cannot be
|
|
3426
|
+
// updated directly, and scrollIntoView is the only way a script
|
|
3427
|
+
// can affect it. So this tries to kludge around the problem by
|
|
3428
|
+
// calling scrollIntoView on the scroll target's line.
|
|
3429
|
+
if (window.visualViewport && window.innerHeight - window.visualViewport.height > 1 &&
|
|
3430
|
+
(rect.top > window.pageYOffset + window.visualViewport.offsetTop + window.visualViewport.height ||
|
|
3431
|
+
rect.bottom < window.pageYOffset + window.visualViewport.offsetTop)) {
|
|
3432
|
+
let line = this.view.docView.lineAt(range.head, 1);
|
|
3433
|
+
if (line)
|
|
3434
|
+
line.dom.scrollIntoView({ block: "nearest" });
|
|
3435
|
+
}
|
|
3422
3436
|
}
|
|
3423
3437
|
lineHasWidget(pos) {
|
|
3424
3438
|
let scan = (child) => child.isWidget() || child.children.some(scan);
|
|
@@ -3767,86 +3781,164 @@ function posAtCoords(view, coords, precise, scanY) {
|
|
|
3767
3781
|
let line = view.docView.lineAt(block.from, 2);
|
|
3768
3782
|
if (!line || line.length != block.length)
|
|
3769
3783
|
line = view.docView.lineAt(block.from, -2);
|
|
3770
|
-
return
|
|
3784
|
+
return new InlineCoordsScan(view, x, y, view.textDirectionAt(block.from)).scanTile(line, block.from);
|
|
3771
3785
|
}
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
//
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3786
|
+
class InlineCoordsScan {
|
|
3787
|
+
constructor(view, x, y, baseDir) {
|
|
3788
|
+
this.view = view;
|
|
3789
|
+
this.x = x;
|
|
3790
|
+
this.y = y;
|
|
3791
|
+
this.baseDir = baseDir;
|
|
3792
|
+
// Cached bidi info
|
|
3793
|
+
this.line = null;
|
|
3794
|
+
this.spans = null;
|
|
3795
|
+
}
|
|
3796
|
+
bidiSpansAt(pos) {
|
|
3797
|
+
if (!this.line || this.line.from > pos || this.line.to < pos) {
|
|
3798
|
+
this.line = this.view.state.doc.lineAt(pos);
|
|
3799
|
+
this.spans = this.view.bidiSpans(this.line);
|
|
3800
|
+
}
|
|
3801
|
+
return this;
|
|
3802
|
+
}
|
|
3803
|
+
baseDirAt(pos, side) {
|
|
3804
|
+
let { line, spans } = this.bidiSpansAt(pos);
|
|
3805
|
+
let level = spans[BidiSpan.find(spans, pos - line.from, -1, side)].level;
|
|
3806
|
+
return level == this.baseDir;
|
|
3807
|
+
}
|
|
3808
|
+
dirAt(pos, side) {
|
|
3809
|
+
let { line, spans } = this.bidiSpansAt(pos);
|
|
3810
|
+
return spans[BidiSpan.find(spans, pos - line.from, -1, side)].dir;
|
|
3811
|
+
}
|
|
3812
|
+
// Used to short-circuit bidi tests for content with a uniform direction
|
|
3813
|
+
bidiIn(from, to) {
|
|
3814
|
+
let { spans, line } = this.bidiSpansAt(from);
|
|
3815
|
+
return spans.length > 1 || spans.length && (spans[0].level != this.baseDir || spans[0].to + line.from < to);
|
|
3816
|
+
}
|
|
3817
|
+
// Scan through the rectangles for the content of a tile with inline
|
|
3818
|
+
// content, looking for one that overlaps the queried position
|
|
3819
|
+
// vertically andis
|
|
3820
|
+
// closest horizontally. The caller is responsible for dividing its
|
|
3821
|
+
// content into N pieces, and pass an array with N+1 positions
|
|
3822
|
+
// (including the position after the last piece). For a text tile,
|
|
3823
|
+
// these will be character clusters, for a composite tile, these
|
|
3824
|
+
// will be child tiles.
|
|
3825
|
+
scan(positions, getRects) {
|
|
3826
|
+
let lo = 0, hi = positions.length - 1, seen = new Set();
|
|
3827
|
+
let bidi = this.bidiIn(positions[0], positions[hi]);
|
|
3828
|
+
let above, below;
|
|
3829
|
+
let closestI = -1, closestDx = 1e9, closestRect;
|
|
3830
|
+
// Because, when the content is bidirectional, a regular binary
|
|
3831
|
+
// search is hard to perform (the content order does not
|
|
3832
|
+
// correspond to visual order), this loop does something between a
|
|
3833
|
+
// regular binary search and a full scan, depending on what it can
|
|
3834
|
+
// get away with. The outer hi/lo bounds are only adjusted for
|
|
3835
|
+
// elements that are part of the base order.
|
|
3836
|
+
//
|
|
3837
|
+
// To make sure all elements inside those bounds are visited,
|
|
3838
|
+
// eventually, we keep a set of seen indices, and if the midpoint
|
|
3839
|
+
// has already been handled, we start in a random index within the
|
|
3840
|
+
// current bounds and scan forward until we find an index that
|
|
3841
|
+
// hasn't been seen yet.
|
|
3842
|
+
search: while (lo < hi) {
|
|
3843
|
+
let dist = hi - lo, mid = (lo + hi) >> 1;
|
|
3844
|
+
adjust: if (seen.has(mid)) {
|
|
3845
|
+
let scan = lo + Math.floor(Math.random() * dist);
|
|
3846
|
+
for (let i = 0; i < dist; i++) {
|
|
3847
|
+
if (!seen.has(scan)) {
|
|
3848
|
+
mid = scan;
|
|
3849
|
+
break adjust;
|
|
3850
|
+
}
|
|
3851
|
+
scan++;
|
|
3852
|
+
if (scan == hi)
|
|
3853
|
+
scan = lo; // Wrap around
|
|
3812
3854
|
}
|
|
3855
|
+
break search; // No index found, we're done
|
|
3813
3856
|
}
|
|
3857
|
+
seen.add(mid);
|
|
3858
|
+
let rects = getRects(mid);
|
|
3859
|
+
if (rects)
|
|
3860
|
+
for (let i = 0; i < rects.length; i++) {
|
|
3861
|
+
let rect = rects[i], side = 0;
|
|
3862
|
+
if (rect.bottom < this.y) {
|
|
3863
|
+
if (!above || above.bottom < rect.bottom)
|
|
3864
|
+
above = rect;
|
|
3865
|
+
side = 1;
|
|
3866
|
+
}
|
|
3867
|
+
else if (rect.top > this.y) {
|
|
3868
|
+
if (!below || below.top > rect.top)
|
|
3869
|
+
below = rect;
|
|
3870
|
+
side = -1;
|
|
3871
|
+
}
|
|
3872
|
+
else {
|
|
3873
|
+
let off = rect.left > this.x ? this.x - rect.left : rect.right < this.x ? this.x - rect.right : 0;
|
|
3874
|
+
let dx = Math.abs(off);
|
|
3875
|
+
if (dx < closestDx) {
|
|
3876
|
+
closestI = mid;
|
|
3877
|
+
closestDx = dx;
|
|
3878
|
+
closestRect = rect;
|
|
3879
|
+
}
|
|
3880
|
+
if (off)
|
|
3881
|
+
side = (off < 0) == (this.baseDir == exports.Direction.LTR) ? -1 : 1;
|
|
3882
|
+
}
|
|
3883
|
+
// Narrow binary search when it is safe to do so
|
|
3884
|
+
if (side == -1 && (!bidi || this.baseDirAt(positions[mid], 1)))
|
|
3885
|
+
hi = mid;
|
|
3886
|
+
else if (side == 1 && (!bidi || this.baseDirAt(positions[mid + 1], -1)))
|
|
3887
|
+
lo = mid + 1;
|
|
3888
|
+
}
|
|
3814
3889
|
}
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
let
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
break;
|
|
3822
|
-
i = next;
|
|
3890
|
+
// If no element with y overlap is found, find the nearest element
|
|
3891
|
+
// on the y axis, move this.y into it, and retry the scan.
|
|
3892
|
+
if (!closestRect) {
|
|
3893
|
+
let side = above && (!below || (this.y - above.bottom < below.top - this.y)) ? above : below;
|
|
3894
|
+
this.y = (side.top + side.bottom) / 2;
|
|
3895
|
+
return this.scan(positions, getRects);
|
|
3823
3896
|
}
|
|
3824
|
-
let
|
|
3825
|
-
return
|
|
3897
|
+
let ltr = (bidi ? this.dirAt(positions[closestI], 1) : this.baseDir) == exports.Direction.LTR;
|
|
3898
|
+
return {
|
|
3899
|
+
i: closestI,
|
|
3900
|
+
// Test whether x is closes to the start or end of this element
|
|
3901
|
+
after: (this.x > (closestRect.left + closestRect.right) / 2) == ltr
|
|
3902
|
+
};
|
|
3826
3903
|
}
|
|
3827
|
-
|
|
3904
|
+
scanText(tile, offset) {
|
|
3905
|
+
let positions = [];
|
|
3906
|
+
for (let i = 0; i < tile.length; i = state.findClusterBreak(tile.text, i))
|
|
3907
|
+
positions.push(offset + i);
|
|
3908
|
+
positions.push(offset + tile.length);
|
|
3909
|
+
let scan = this.scan(positions, i => {
|
|
3910
|
+
let off = positions[i] - offset, end = positions[i + 1] - offset;
|
|
3911
|
+
return textRange(tile.dom, off, end).getClientRects();
|
|
3912
|
+
});
|
|
3913
|
+
return scan.after ? new PosAssoc(positions[scan.i + 1], -1) : new PosAssoc(positions[scan.i], 1);
|
|
3914
|
+
}
|
|
3915
|
+
scanTile(tile, offset) {
|
|
3828
3916
|
if (!tile.length)
|
|
3829
3917
|
return new PosAssoc(offset, 1);
|
|
3830
|
-
|
|
3918
|
+
if (tile.children.length == 1) { // Short-circuit single-child tiles
|
|
3919
|
+
let child = tile.children[0];
|
|
3920
|
+
if (child.isText())
|
|
3921
|
+
return this.scanText(child, offset);
|
|
3922
|
+
else if (child.isComposite())
|
|
3923
|
+
return this.scanTile(child, offset);
|
|
3924
|
+
}
|
|
3925
|
+
let positions = [offset];
|
|
3926
|
+
for (let i = 0, pos = offset; i < tile.children.length; i++)
|
|
3927
|
+
positions.push(pos += tile.children[i].length);
|
|
3928
|
+
let scan = this.scan(positions, i => {
|
|
3831
3929
|
let child = tile.children[i];
|
|
3832
3930
|
if (child.flags & 48 /* TileFlag.PointWidget */)
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
let after = (x > (closestRect.left + closestRect.right) / 2) == (dirAt(view, closest + offset) == exports.Direction.LTR);
|
|
3843
|
-
return after ? new PosAssoc(innerOff + inner.length, -1) : new PosAssoc(innerOff, 1);
|
|
3931
|
+
return null;
|
|
3932
|
+
return (child.dom.nodeType == 1 ? child.dom : textRange(child.dom, 0, child.length)).getClientRects();
|
|
3933
|
+
});
|
|
3934
|
+
let child = tile.children[scan.i], pos = positions[scan.i];
|
|
3935
|
+
if (child.isText())
|
|
3936
|
+
return this.scanText(child, pos);
|
|
3937
|
+
if (child.isComposite())
|
|
3938
|
+
return this.scanTile(child, pos);
|
|
3939
|
+
return scan.after ? new PosAssoc(positions[scan.i + 1], -1) : new PosAssoc(pos, 1);
|
|
3844
3940
|
}
|
|
3845
3941
|
}
|
|
3846
|
-
function dirAt(view, pos) {
|
|
3847
|
-
let line = view.state.doc.lineAt(pos), spans = view.bidiSpans(line);
|
|
3848
|
-
return spans[BidiSpan.find(view.bidiSpans(line), pos - line.from, -1, 1)].dir;
|
|
3849
|
-
}
|
|
3850
3942
|
|
|
3851
3943
|
const LineBreakPlaceholder = "\uffff";
|
|
3852
3944
|
class DOMReader {
|
|
@@ -5000,8 +5092,7 @@ handlers.copy = handlers.cut = (view, event) => {
|
|
|
5000
5092
|
// spans multiple elements including this CodeMirror. The copy event may
|
|
5001
5093
|
// bubble through CodeMirror (e.g. when CodeMirror is the first or the last
|
|
5002
5094
|
// element in the selection), but we should let the parent handle it.
|
|
5003
|
-
|
|
5004
|
-
if (domSel && !hasSelection(view.contentDOM, domSel))
|
|
5095
|
+
if (!hasSelection(view.contentDOM, view.observer.selectionRange))
|
|
5005
5096
|
return false;
|
|
5006
5097
|
let { text, ranges, linewise } = copiedRange(view.state);
|
|
5007
5098
|
if (!text && !linewise)
|
|
@@ -6125,7 +6216,7 @@ class ViewState {
|
|
|
6125
6216
|
let oracle = this.heightOracle;
|
|
6126
6217
|
let whiteSpace = style.whiteSpace;
|
|
6127
6218
|
this.defaultTextDirection = style.direction == "rtl" ? exports.Direction.RTL : exports.Direction.LTR;
|
|
6128
|
-
let refresh = this.heightOracle.mustRefreshForWrapping(whiteSpace) || this.mustMeasureContent;
|
|
6219
|
+
let refresh = this.heightOracle.mustRefreshForWrapping(whiteSpace) || this.mustMeasureContent === "refresh";
|
|
6129
6220
|
let domRect = dom.getBoundingClientRect();
|
|
6130
6221
|
let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != domRect.height;
|
|
6131
6222
|
this.contentDOMHeight = domRect.height;
|
|
@@ -7702,7 +7793,7 @@ class EditorView {
|
|
|
7702
7793
|
this.requestMeasure();
|
|
7703
7794
|
if ((_a = document.fonts) === null || _a === void 0 ? void 0 : _a.ready)
|
|
7704
7795
|
document.fonts.ready.then(() => {
|
|
7705
|
-
this.viewState.mustMeasureContent =
|
|
7796
|
+
this.viewState.mustMeasureContent = "refresh";
|
|
7706
7797
|
this.requestMeasure();
|
|
7707
7798
|
});
|
|
7708
7799
|
}
|
package/dist/index.js
CHANGED
|
@@ -2787,9 +2787,10 @@ class TileUpdate {
|
|
|
2787
2787
|
marks.push(tile);
|
|
2788
2788
|
else if (tile === null || tile === void 0 ? void 0 : tile.isLine())
|
|
2789
2789
|
line = tile;
|
|
2790
|
+
else if (tile instanceof BlockWrapperTile) ; // Ignore
|
|
2790
2791
|
else if (parent.nodeName == "DIV" && !line && parent != this.view.contentDOM)
|
|
2791
2792
|
line = new LineTile(parent, lineBaseAttrs);
|
|
2792
|
-
else
|
|
2793
|
+
else if (!line)
|
|
2793
2794
|
marks.push(MarkTile.of(new MarkDecoration({ tagName: parent.nodeName.toLowerCase(), attributes: getAttrs(parent) }), parent));
|
|
2794
2795
|
}
|
|
2795
2796
|
return { line: line, marks };
|
|
@@ -3415,6 +3416,19 @@ class DocView {
|
|
|
3415
3416
|
};
|
|
3416
3417
|
let { offsetWidth, offsetHeight } = this.view.scrollDOM;
|
|
3417
3418
|
scrollRectIntoView(this.view.scrollDOM, targetRect, range.head < range.anchor ? -1 : 1, target.x, target.y, Math.max(Math.min(target.xMargin, offsetWidth), -offsetWidth), Math.max(Math.min(target.yMargin, offsetHeight), -offsetHeight), this.view.textDirection == Direction.LTR);
|
|
3419
|
+
// On mobile browsers, the visual viewport may be smaller than the
|
|
3420
|
+
// actual reported viewport, causing scrollRectIntoView to fail to
|
|
3421
|
+
// scroll properly. Unfortunately, this visual viewport cannot be
|
|
3422
|
+
// updated directly, and scrollIntoView is the only way a script
|
|
3423
|
+
// can affect it. So this tries to kludge around the problem by
|
|
3424
|
+
// calling scrollIntoView on the scroll target's line.
|
|
3425
|
+
if (window.visualViewport && window.innerHeight - window.visualViewport.height > 1 &&
|
|
3426
|
+
(rect.top > window.pageYOffset + window.visualViewport.offsetTop + window.visualViewport.height ||
|
|
3427
|
+
rect.bottom < window.pageYOffset + window.visualViewport.offsetTop)) {
|
|
3428
|
+
let line = this.view.docView.lineAt(range.head, 1);
|
|
3429
|
+
if (line)
|
|
3430
|
+
line.dom.scrollIntoView({ block: "nearest" });
|
|
3431
|
+
}
|
|
3418
3432
|
}
|
|
3419
3433
|
lineHasWidget(pos) {
|
|
3420
3434
|
let scan = (child) => child.isWidget() || child.children.some(scan);
|
|
@@ -3763,86 +3777,164 @@ function posAtCoords(view, coords, precise, scanY) {
|
|
|
3763
3777
|
let line = view.docView.lineAt(block.from, 2);
|
|
3764
3778
|
if (!line || line.length != block.length)
|
|
3765
3779
|
line = view.docView.lineAt(block.from, -2);
|
|
3766
|
-
return
|
|
3780
|
+
return new InlineCoordsScan(view, x, y, view.textDirectionAt(block.from)).scanTile(line, block.from);
|
|
3767
3781
|
}
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
//
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3782
|
+
class InlineCoordsScan {
|
|
3783
|
+
constructor(view, x, y, baseDir) {
|
|
3784
|
+
this.view = view;
|
|
3785
|
+
this.x = x;
|
|
3786
|
+
this.y = y;
|
|
3787
|
+
this.baseDir = baseDir;
|
|
3788
|
+
// Cached bidi info
|
|
3789
|
+
this.line = null;
|
|
3790
|
+
this.spans = null;
|
|
3791
|
+
}
|
|
3792
|
+
bidiSpansAt(pos) {
|
|
3793
|
+
if (!this.line || this.line.from > pos || this.line.to < pos) {
|
|
3794
|
+
this.line = this.view.state.doc.lineAt(pos);
|
|
3795
|
+
this.spans = this.view.bidiSpans(this.line);
|
|
3796
|
+
}
|
|
3797
|
+
return this;
|
|
3798
|
+
}
|
|
3799
|
+
baseDirAt(pos, side) {
|
|
3800
|
+
let { line, spans } = this.bidiSpansAt(pos);
|
|
3801
|
+
let level = spans[BidiSpan.find(spans, pos - line.from, -1, side)].level;
|
|
3802
|
+
return level == this.baseDir;
|
|
3803
|
+
}
|
|
3804
|
+
dirAt(pos, side) {
|
|
3805
|
+
let { line, spans } = this.bidiSpansAt(pos);
|
|
3806
|
+
return spans[BidiSpan.find(spans, pos - line.from, -1, side)].dir;
|
|
3807
|
+
}
|
|
3808
|
+
// Used to short-circuit bidi tests for content with a uniform direction
|
|
3809
|
+
bidiIn(from, to) {
|
|
3810
|
+
let { spans, line } = this.bidiSpansAt(from);
|
|
3811
|
+
return spans.length > 1 || spans.length && (spans[0].level != this.baseDir || spans[0].to + line.from < to);
|
|
3812
|
+
}
|
|
3813
|
+
// Scan through the rectangles for the content of a tile with inline
|
|
3814
|
+
// content, looking for one that overlaps the queried position
|
|
3815
|
+
// vertically andis
|
|
3816
|
+
// closest horizontally. The caller is responsible for dividing its
|
|
3817
|
+
// content into N pieces, and pass an array with N+1 positions
|
|
3818
|
+
// (including the position after the last piece). For a text tile,
|
|
3819
|
+
// these will be character clusters, for a composite tile, these
|
|
3820
|
+
// will be child tiles.
|
|
3821
|
+
scan(positions, getRects) {
|
|
3822
|
+
let lo = 0, hi = positions.length - 1, seen = new Set();
|
|
3823
|
+
let bidi = this.bidiIn(positions[0], positions[hi]);
|
|
3824
|
+
let above, below;
|
|
3825
|
+
let closestI = -1, closestDx = 1e9, closestRect;
|
|
3826
|
+
// Because, when the content is bidirectional, a regular binary
|
|
3827
|
+
// search is hard to perform (the content order does not
|
|
3828
|
+
// correspond to visual order), this loop does something between a
|
|
3829
|
+
// regular binary search and a full scan, depending on what it can
|
|
3830
|
+
// get away with. The outer hi/lo bounds are only adjusted for
|
|
3831
|
+
// elements that are part of the base order.
|
|
3832
|
+
//
|
|
3833
|
+
// To make sure all elements inside those bounds are visited,
|
|
3834
|
+
// eventually, we keep a set of seen indices, and if the midpoint
|
|
3835
|
+
// has already been handled, we start in a random index within the
|
|
3836
|
+
// current bounds and scan forward until we find an index that
|
|
3837
|
+
// hasn't been seen yet.
|
|
3838
|
+
search: while (lo < hi) {
|
|
3839
|
+
let dist = hi - lo, mid = (lo + hi) >> 1;
|
|
3840
|
+
adjust: if (seen.has(mid)) {
|
|
3841
|
+
let scan = lo + Math.floor(Math.random() * dist);
|
|
3842
|
+
for (let i = 0; i < dist; i++) {
|
|
3843
|
+
if (!seen.has(scan)) {
|
|
3844
|
+
mid = scan;
|
|
3845
|
+
break adjust;
|
|
3846
|
+
}
|
|
3847
|
+
scan++;
|
|
3848
|
+
if (scan == hi)
|
|
3849
|
+
scan = lo; // Wrap around
|
|
3808
3850
|
}
|
|
3851
|
+
break search; // No index found, we're done
|
|
3809
3852
|
}
|
|
3853
|
+
seen.add(mid);
|
|
3854
|
+
let rects = getRects(mid);
|
|
3855
|
+
if (rects)
|
|
3856
|
+
for (let i = 0; i < rects.length; i++) {
|
|
3857
|
+
let rect = rects[i], side = 0;
|
|
3858
|
+
if (rect.bottom < this.y) {
|
|
3859
|
+
if (!above || above.bottom < rect.bottom)
|
|
3860
|
+
above = rect;
|
|
3861
|
+
side = 1;
|
|
3862
|
+
}
|
|
3863
|
+
else if (rect.top > this.y) {
|
|
3864
|
+
if (!below || below.top > rect.top)
|
|
3865
|
+
below = rect;
|
|
3866
|
+
side = -1;
|
|
3867
|
+
}
|
|
3868
|
+
else {
|
|
3869
|
+
let off = rect.left > this.x ? this.x - rect.left : rect.right < this.x ? this.x - rect.right : 0;
|
|
3870
|
+
let dx = Math.abs(off);
|
|
3871
|
+
if (dx < closestDx) {
|
|
3872
|
+
closestI = mid;
|
|
3873
|
+
closestDx = dx;
|
|
3874
|
+
closestRect = rect;
|
|
3875
|
+
}
|
|
3876
|
+
if (off)
|
|
3877
|
+
side = (off < 0) == (this.baseDir == Direction.LTR) ? -1 : 1;
|
|
3878
|
+
}
|
|
3879
|
+
// Narrow binary search when it is safe to do so
|
|
3880
|
+
if (side == -1 && (!bidi || this.baseDirAt(positions[mid], 1)))
|
|
3881
|
+
hi = mid;
|
|
3882
|
+
else if (side == 1 && (!bidi || this.baseDirAt(positions[mid + 1], -1)))
|
|
3883
|
+
lo = mid + 1;
|
|
3884
|
+
}
|
|
3810
3885
|
}
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
let
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
break;
|
|
3818
|
-
i = next;
|
|
3886
|
+
// If no element with y overlap is found, find the nearest element
|
|
3887
|
+
// on the y axis, move this.y into it, and retry the scan.
|
|
3888
|
+
if (!closestRect) {
|
|
3889
|
+
let side = above && (!below || (this.y - above.bottom < below.top - this.y)) ? above : below;
|
|
3890
|
+
this.y = (side.top + side.bottom) / 2;
|
|
3891
|
+
return this.scan(positions, getRects);
|
|
3819
3892
|
}
|
|
3820
|
-
let
|
|
3821
|
-
return
|
|
3893
|
+
let ltr = (bidi ? this.dirAt(positions[closestI], 1) : this.baseDir) == Direction.LTR;
|
|
3894
|
+
return {
|
|
3895
|
+
i: closestI,
|
|
3896
|
+
// Test whether x is closes to the start or end of this element
|
|
3897
|
+
after: (this.x > (closestRect.left + closestRect.right) / 2) == ltr
|
|
3898
|
+
};
|
|
3822
3899
|
}
|
|
3823
|
-
|
|
3900
|
+
scanText(tile, offset) {
|
|
3901
|
+
let positions = [];
|
|
3902
|
+
for (let i = 0; i < tile.length; i = findClusterBreak(tile.text, i))
|
|
3903
|
+
positions.push(offset + i);
|
|
3904
|
+
positions.push(offset + tile.length);
|
|
3905
|
+
let scan = this.scan(positions, i => {
|
|
3906
|
+
let off = positions[i] - offset, end = positions[i + 1] - offset;
|
|
3907
|
+
return textRange(tile.dom, off, end).getClientRects();
|
|
3908
|
+
});
|
|
3909
|
+
return scan.after ? new PosAssoc(positions[scan.i + 1], -1) : new PosAssoc(positions[scan.i], 1);
|
|
3910
|
+
}
|
|
3911
|
+
scanTile(tile, offset) {
|
|
3824
3912
|
if (!tile.length)
|
|
3825
3913
|
return new PosAssoc(offset, 1);
|
|
3826
|
-
|
|
3914
|
+
if (tile.children.length == 1) { // Short-circuit single-child tiles
|
|
3915
|
+
let child = tile.children[0];
|
|
3916
|
+
if (child.isText())
|
|
3917
|
+
return this.scanText(child, offset);
|
|
3918
|
+
else if (child.isComposite())
|
|
3919
|
+
return this.scanTile(child, offset);
|
|
3920
|
+
}
|
|
3921
|
+
let positions = [offset];
|
|
3922
|
+
for (let i = 0, pos = offset; i < tile.children.length; i++)
|
|
3923
|
+
positions.push(pos += tile.children[i].length);
|
|
3924
|
+
let scan = this.scan(positions, i => {
|
|
3827
3925
|
let child = tile.children[i];
|
|
3828
3926
|
if (child.flags & 48 /* TileFlag.PointWidget */)
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
let after = (x > (closestRect.left + closestRect.right) / 2) == (dirAt(view, closest + offset) == Direction.LTR);
|
|
3839
|
-
return after ? new PosAssoc(innerOff + inner.length, -1) : new PosAssoc(innerOff, 1);
|
|
3927
|
+
return null;
|
|
3928
|
+
return (child.dom.nodeType == 1 ? child.dom : textRange(child.dom, 0, child.length)).getClientRects();
|
|
3929
|
+
});
|
|
3930
|
+
let child = tile.children[scan.i], pos = positions[scan.i];
|
|
3931
|
+
if (child.isText())
|
|
3932
|
+
return this.scanText(child, pos);
|
|
3933
|
+
if (child.isComposite())
|
|
3934
|
+
return this.scanTile(child, pos);
|
|
3935
|
+
return scan.after ? new PosAssoc(positions[scan.i + 1], -1) : new PosAssoc(pos, 1);
|
|
3840
3936
|
}
|
|
3841
3937
|
}
|
|
3842
|
-
function dirAt(view, pos) {
|
|
3843
|
-
let line = view.state.doc.lineAt(pos), spans = view.bidiSpans(line);
|
|
3844
|
-
return spans[BidiSpan.find(view.bidiSpans(line), pos - line.from, -1, 1)].dir;
|
|
3845
|
-
}
|
|
3846
3938
|
|
|
3847
3939
|
const LineBreakPlaceholder = "\uffff";
|
|
3848
3940
|
class DOMReader {
|
|
@@ -4996,8 +5088,7 @@ handlers.copy = handlers.cut = (view, event) => {
|
|
|
4996
5088
|
// spans multiple elements including this CodeMirror. The copy event may
|
|
4997
5089
|
// bubble through CodeMirror (e.g. when CodeMirror is the first or the last
|
|
4998
5090
|
// element in the selection), but we should let the parent handle it.
|
|
4999
|
-
|
|
5000
|
-
if (domSel && !hasSelection(view.contentDOM, domSel))
|
|
5091
|
+
if (!hasSelection(view.contentDOM, view.observer.selectionRange))
|
|
5001
5092
|
return false;
|
|
5002
5093
|
let { text, ranges, linewise } = copiedRange(view.state);
|
|
5003
5094
|
if (!text && !linewise)
|
|
@@ -6120,7 +6211,7 @@ class ViewState {
|
|
|
6120
6211
|
let oracle = this.heightOracle;
|
|
6121
6212
|
let whiteSpace = style.whiteSpace;
|
|
6122
6213
|
this.defaultTextDirection = style.direction == "rtl" ? Direction.RTL : Direction.LTR;
|
|
6123
|
-
let refresh = this.heightOracle.mustRefreshForWrapping(whiteSpace) || this.mustMeasureContent;
|
|
6214
|
+
let refresh = this.heightOracle.mustRefreshForWrapping(whiteSpace) || this.mustMeasureContent === "refresh";
|
|
6124
6215
|
let domRect = dom.getBoundingClientRect();
|
|
6125
6216
|
let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != domRect.height;
|
|
6126
6217
|
this.contentDOMHeight = domRect.height;
|
|
@@ -7697,7 +7788,7 @@ class EditorView {
|
|
|
7697
7788
|
this.requestMeasure();
|
|
7698
7789
|
if ((_a = document.fonts) === null || _a === void 0 ? void 0 : _a.ready)
|
|
7699
7790
|
document.fonts.ready.then(() => {
|
|
7700
|
-
this.viewState.mustMeasureContent =
|
|
7791
|
+
this.viewState.mustMeasureContent = "refresh";
|
|
7701
7792
|
this.requestMeasure();
|
|
7702
7793
|
});
|
|
7703
7794
|
}
|