@codemirror/view 6.39.12 → 6.39.14
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 +16 -0
- package/dist/index.cjs +149 -72
- package/dist/index.js +149 -72
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
## 6.39.14 (2026-02-12)
|
|
2
|
+
|
|
3
|
+
### Bug fixes
|
|
4
|
+
|
|
5
|
+
Improve performance of `posAtCoords` on long lines.
|
|
6
|
+
|
|
7
|
+
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.
|
|
8
|
+
|
|
9
|
+
## 6.39.13 (2026-02-08)
|
|
10
|
+
|
|
11
|
+
### Bug fixes
|
|
12
|
+
|
|
13
|
+
Fix an issue where a widget at start or end of line, when wrapped to cover that whole line, could block vertical cursor motion.
|
|
14
|
+
|
|
15
|
+
Fix an issue `EditorView.moveVertically` that would sometimes cause selection-extending vertical motion to get stuck on line wrapping points.
|
|
16
|
+
|
|
1
17
|
## 6.39.12 (2026-01-30)
|
|
2
18
|
|
|
3
19
|
### Bug fixes
|
package/dist/index.cjs
CHANGED
|
@@ -3660,7 +3660,7 @@ function moveVertically(view, start, forward, distance) {
|
|
|
3660
3660
|
return state.EditorSelection.cursor(startPos, start.assoc);
|
|
3661
3661
|
let goal = start.goalColumn, startY;
|
|
3662
3662
|
let rect = view.contentDOM.getBoundingClientRect();
|
|
3663
|
-
let startCoords = view.coordsAtPos(startPos, start.assoc || -1), docTop = view.documentTop;
|
|
3663
|
+
let startCoords = view.coordsAtPos(startPos, (start.empty ? start.assoc : 0) || (forward ? 1 : -1)), docTop = view.documentTop;
|
|
3664
3664
|
if (startCoords) {
|
|
3665
3665
|
if (goal == null)
|
|
3666
3666
|
goal = startCoords.left - rect.left;
|
|
@@ -3744,7 +3744,7 @@ function posAtCoords(view, coords, precise, scanY) {
|
|
|
3744
3744
|
if (scanY < 0 ? block.to < view.viewport.from : block.from > view.viewport.to)
|
|
3745
3745
|
break;
|
|
3746
3746
|
// Check whether we aren't landing on the top/bottom padding of the line
|
|
3747
|
-
let rect = view.docView.coordsAt(scanY < 0 ? block.from : block.to, scanY);
|
|
3747
|
+
let rect = view.docView.coordsAt(scanY < 0 ? block.from : block.to, scanY > 0 ? -1 : 1);
|
|
3748
3748
|
if (rect && (scanY < 0 ? rect.top <= yOffset + docTop : rect.bottom >= yOffset + docTop))
|
|
3749
3749
|
break;
|
|
3750
3750
|
}
|
|
@@ -3767,86 +3767,164 @@ function posAtCoords(view, coords, precise, scanY) {
|
|
|
3767
3767
|
let line = view.docView.lineAt(block.from, 2);
|
|
3768
3768
|
if (!line || line.length != block.length)
|
|
3769
3769
|
line = view.docView.lineAt(block.from, -2);
|
|
3770
|
-
return
|
|
3770
|
+
return new InlineCoordsScan(view, x, y, view.textDirectionAt(block.from)).scanTile(line, block.from);
|
|
3771
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
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3772
|
+
class InlineCoordsScan {
|
|
3773
|
+
constructor(view, x, y, baseDir) {
|
|
3774
|
+
this.view = view;
|
|
3775
|
+
this.x = x;
|
|
3776
|
+
this.y = y;
|
|
3777
|
+
this.baseDir = baseDir;
|
|
3778
|
+
// Cached bidi info
|
|
3779
|
+
this.line = null;
|
|
3780
|
+
this.spans = null;
|
|
3781
|
+
}
|
|
3782
|
+
bidiSpansAt(pos) {
|
|
3783
|
+
if (!this.line || this.line.from > pos || this.line.to < pos) {
|
|
3784
|
+
this.line = this.view.state.doc.lineAt(pos);
|
|
3785
|
+
this.spans = this.view.bidiSpans(this.line);
|
|
3786
|
+
}
|
|
3787
|
+
return this;
|
|
3788
|
+
}
|
|
3789
|
+
baseDirAt(pos, side) {
|
|
3790
|
+
let { line, spans } = this.bidiSpansAt(pos);
|
|
3791
|
+
let level = spans[BidiSpan.find(spans, pos - line.from, -1, side)].level;
|
|
3792
|
+
return level == this.baseDir;
|
|
3793
|
+
}
|
|
3794
|
+
dirAt(pos, side) {
|
|
3795
|
+
let { line, spans } = this.bidiSpansAt(pos);
|
|
3796
|
+
return spans[BidiSpan.find(spans, pos - line.from, -1, side)].dir;
|
|
3797
|
+
}
|
|
3798
|
+
// Used to short-circuit bidi tests for content with a uniform direction
|
|
3799
|
+
bidiIn(from, to) {
|
|
3800
|
+
let { spans, line } = this.bidiSpansAt(from);
|
|
3801
|
+
return spans.length > 1 || spans.length && (spans[0].level != this.baseDir || spans[0].to + line.from < to);
|
|
3802
|
+
}
|
|
3803
|
+
// Scan through the rectangles for the content of a tile with inline
|
|
3804
|
+
// content, looking for one that overlaps the queried position
|
|
3805
|
+
// vertically andis
|
|
3806
|
+
// closest horizontally. The caller is responsible for dividing its
|
|
3807
|
+
// content into N pieces, and pass an array with N+1 positions
|
|
3808
|
+
// (including the position after the last piece). For a text tile,
|
|
3809
|
+
// these will be character clusters, for a composite tile, these
|
|
3810
|
+
// will be child tiles.
|
|
3811
|
+
scan(positions, getRects) {
|
|
3812
|
+
let lo = 0, hi = positions.length - 1, seen = new Set();
|
|
3813
|
+
let bidi = this.bidiIn(positions[0], positions[hi]);
|
|
3814
|
+
let above, below;
|
|
3815
|
+
let closestI = -1, closestDx = 1e9, closestRect;
|
|
3816
|
+
// Because, when the content is bidirectional, a regular binary
|
|
3817
|
+
// search is hard to perform (the content order does not
|
|
3818
|
+
// correspond to visual order), this loop does something between a
|
|
3819
|
+
// regular binary search and a full scan, depending on what it can
|
|
3820
|
+
// get away with. The outer hi/lo bounds are only adjusted for
|
|
3821
|
+
// elements that are part of the base order.
|
|
3822
|
+
//
|
|
3823
|
+
// To make sure all elements inside those bounds are visited,
|
|
3824
|
+
// eventually, we keep a set of seen indices, and if the midpoint
|
|
3825
|
+
// has already been handled, we start in a random index within the
|
|
3826
|
+
// current bounds and scan forward until we find an index that
|
|
3827
|
+
// hasn't been seen yet.
|
|
3828
|
+
search: while (lo < hi) {
|
|
3829
|
+
let dist = hi - lo, mid = (lo + hi) >> 1;
|
|
3830
|
+
adjust: if (seen.has(mid)) {
|
|
3831
|
+
let scan = lo + Math.floor(Math.random() * dist);
|
|
3832
|
+
for (let i = 0; i < dist; i++) {
|
|
3833
|
+
if (!seen.has(scan)) {
|
|
3834
|
+
mid = scan;
|
|
3835
|
+
break adjust;
|
|
3836
|
+
}
|
|
3837
|
+
scan++;
|
|
3838
|
+
if (scan == hi)
|
|
3839
|
+
scan = lo; // Wrap around
|
|
3812
3840
|
}
|
|
3841
|
+
break search; // No index found, we're done
|
|
3813
3842
|
}
|
|
3843
|
+
seen.add(mid);
|
|
3844
|
+
let rects = getRects(mid);
|
|
3845
|
+
if (rects)
|
|
3846
|
+
for (let i = 0; i < rects.length; i++) {
|
|
3847
|
+
let rect = rects[i], side = 0;
|
|
3848
|
+
if (rect.bottom < this.y) {
|
|
3849
|
+
if (!above || above.bottom < rect.bottom)
|
|
3850
|
+
above = rect;
|
|
3851
|
+
side = 1;
|
|
3852
|
+
}
|
|
3853
|
+
else if (rect.top > this.y) {
|
|
3854
|
+
if (!below || below.top > rect.top)
|
|
3855
|
+
below = rect;
|
|
3856
|
+
side = -1;
|
|
3857
|
+
}
|
|
3858
|
+
else {
|
|
3859
|
+
let off = rect.left > this.x ? this.x - rect.left : rect.right < this.x ? this.x - rect.right : 0;
|
|
3860
|
+
let dx = Math.abs(off);
|
|
3861
|
+
if (dx < closestDx) {
|
|
3862
|
+
closestI = mid;
|
|
3863
|
+
closestDx = dx;
|
|
3864
|
+
closestRect = rect;
|
|
3865
|
+
}
|
|
3866
|
+
if (off)
|
|
3867
|
+
side = (off < 0) == (this.baseDir == exports.Direction.LTR) ? -1 : 1;
|
|
3868
|
+
}
|
|
3869
|
+
// Narrow binary search when it is safe to do so
|
|
3870
|
+
if (side == -1 && (!bidi || this.baseDirAt(positions[mid], 1)))
|
|
3871
|
+
hi = mid;
|
|
3872
|
+
else if (side == 1 && (!bidi || this.baseDirAt(positions[mid + 1], -1)))
|
|
3873
|
+
lo = mid + 1;
|
|
3874
|
+
}
|
|
3814
3875
|
}
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
let
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
break;
|
|
3822
|
-
i = next;
|
|
3876
|
+
// If no element with y overlap is found, find the nearest element
|
|
3877
|
+
// on the y axis, move this.y into it, and retry the scan.
|
|
3878
|
+
if (!closestRect) {
|
|
3879
|
+
let side = above && (!below || (this.y - above.bottom < below.top - this.y)) ? above : below;
|
|
3880
|
+
this.y = (side.top + side.bottom) / 2;
|
|
3881
|
+
return this.scan(positions, getRects);
|
|
3823
3882
|
}
|
|
3824
|
-
let
|
|
3825
|
-
return
|
|
3883
|
+
let ltr = (bidi ? this.dirAt(positions[closestI], 1) : this.baseDir) == exports.Direction.LTR;
|
|
3884
|
+
return {
|
|
3885
|
+
i: closestI,
|
|
3886
|
+
// Test whether x is closes to the start or end of this element
|
|
3887
|
+
after: (this.x > (closestRect.left + closestRect.right) / 2) == ltr
|
|
3888
|
+
};
|
|
3826
3889
|
}
|
|
3827
|
-
|
|
3890
|
+
scanText(tile, offset) {
|
|
3891
|
+
let positions = [];
|
|
3892
|
+
for (let i = 0; i < tile.length; i = state.findClusterBreak(tile.text, i))
|
|
3893
|
+
positions.push(offset + i);
|
|
3894
|
+
positions.push(offset + tile.length);
|
|
3895
|
+
let scan = this.scan(positions, i => {
|
|
3896
|
+
let off = positions[i] - offset, end = positions[i + 1] - offset;
|
|
3897
|
+
return textRange(tile.dom, off, end).getClientRects();
|
|
3898
|
+
});
|
|
3899
|
+
return scan.after ? new PosAssoc(positions[scan.i + 1], -1) : new PosAssoc(positions[scan.i], 1);
|
|
3900
|
+
}
|
|
3901
|
+
scanTile(tile, offset) {
|
|
3828
3902
|
if (!tile.length)
|
|
3829
3903
|
return new PosAssoc(offset, 1);
|
|
3830
|
-
|
|
3904
|
+
if (tile.children.length == 1) { // Short-circuit single-child tiles
|
|
3905
|
+
let child = tile.children[0];
|
|
3906
|
+
if (child.isText())
|
|
3907
|
+
return this.scanText(child, offset);
|
|
3908
|
+
else if (child.isComposite())
|
|
3909
|
+
return this.scanTile(child, offset);
|
|
3910
|
+
}
|
|
3911
|
+
let positions = [offset];
|
|
3912
|
+
for (let i = 0, pos = offset; i < tile.children.length; i++)
|
|
3913
|
+
positions.push(pos += tile.children[i].length);
|
|
3914
|
+
let scan = this.scan(positions, i => {
|
|
3831
3915
|
let child = tile.children[i];
|
|
3832
3916
|
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);
|
|
3917
|
+
return null;
|
|
3918
|
+
return (child.dom.nodeType == 1 ? child.dom : textRange(child.dom, 0, child.length)).getClientRects();
|
|
3919
|
+
});
|
|
3920
|
+
let child = tile.children[scan.i], pos = positions[scan.i];
|
|
3921
|
+
if (child.isText())
|
|
3922
|
+
return this.scanText(child, pos);
|
|
3923
|
+
if (child.isComposite())
|
|
3924
|
+
return this.scanTile(child, pos);
|
|
3925
|
+
return scan.after ? new PosAssoc(positions[scan.i + 1], -1) : new PosAssoc(pos, 1);
|
|
3844
3926
|
}
|
|
3845
3927
|
}
|
|
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
3928
|
|
|
3851
3929
|
const LineBreakPlaceholder = "\uffff";
|
|
3852
3930
|
class DOMReader {
|
|
@@ -5000,8 +5078,7 @@ handlers.copy = handlers.cut = (view, event) => {
|
|
|
5000
5078
|
// spans multiple elements including this CodeMirror. The copy event may
|
|
5001
5079
|
// bubble through CodeMirror (e.g. when CodeMirror is the first or the last
|
|
5002
5080
|
// element in the selection), but we should let the parent handle it.
|
|
5003
|
-
|
|
5004
|
-
if (domSel && !hasSelection(view.contentDOM, domSel))
|
|
5081
|
+
if (!hasSelection(view.contentDOM, view.observer.selectionRange))
|
|
5005
5082
|
return false;
|
|
5006
5083
|
let { text, ranges, linewise } = copiedRange(view.state);
|
|
5007
5084
|
if (!text && !linewise)
|
package/dist/index.js
CHANGED
|
@@ -3656,7 +3656,7 @@ function moveVertically(view, start, forward, distance) {
|
|
|
3656
3656
|
return EditorSelection.cursor(startPos, start.assoc);
|
|
3657
3657
|
let goal = start.goalColumn, startY;
|
|
3658
3658
|
let rect = view.contentDOM.getBoundingClientRect();
|
|
3659
|
-
let startCoords = view.coordsAtPos(startPos, start.assoc || -1), docTop = view.documentTop;
|
|
3659
|
+
let startCoords = view.coordsAtPos(startPos, (start.empty ? start.assoc : 0) || (forward ? 1 : -1)), docTop = view.documentTop;
|
|
3660
3660
|
if (startCoords) {
|
|
3661
3661
|
if (goal == null)
|
|
3662
3662
|
goal = startCoords.left - rect.left;
|
|
@@ -3740,7 +3740,7 @@ function posAtCoords(view, coords, precise, scanY) {
|
|
|
3740
3740
|
if (scanY < 0 ? block.to < view.viewport.from : block.from > view.viewport.to)
|
|
3741
3741
|
break;
|
|
3742
3742
|
// Check whether we aren't landing on the top/bottom padding of the line
|
|
3743
|
-
let rect = view.docView.coordsAt(scanY < 0 ? block.from : block.to, scanY);
|
|
3743
|
+
let rect = view.docView.coordsAt(scanY < 0 ? block.from : block.to, scanY > 0 ? -1 : 1);
|
|
3744
3744
|
if (rect && (scanY < 0 ? rect.top <= yOffset + docTop : rect.bottom >= yOffset + docTop))
|
|
3745
3745
|
break;
|
|
3746
3746
|
}
|
|
@@ -3763,86 +3763,164 @@ function posAtCoords(view, coords, precise, scanY) {
|
|
|
3763
3763
|
let line = view.docView.lineAt(block.from, 2);
|
|
3764
3764
|
if (!line || line.length != block.length)
|
|
3765
3765
|
line = view.docView.lineAt(block.from, -2);
|
|
3766
|
-
return
|
|
3766
|
+
return new InlineCoordsScan(view, x, y, view.textDirectionAt(block.from)).scanTile(line, block.from);
|
|
3767
3767
|
}
|
|
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
|
-
|
|
3768
|
+
class InlineCoordsScan {
|
|
3769
|
+
constructor(view, x, y, baseDir) {
|
|
3770
|
+
this.view = view;
|
|
3771
|
+
this.x = x;
|
|
3772
|
+
this.y = y;
|
|
3773
|
+
this.baseDir = baseDir;
|
|
3774
|
+
// Cached bidi info
|
|
3775
|
+
this.line = null;
|
|
3776
|
+
this.spans = null;
|
|
3777
|
+
}
|
|
3778
|
+
bidiSpansAt(pos) {
|
|
3779
|
+
if (!this.line || this.line.from > pos || this.line.to < pos) {
|
|
3780
|
+
this.line = this.view.state.doc.lineAt(pos);
|
|
3781
|
+
this.spans = this.view.bidiSpans(this.line);
|
|
3782
|
+
}
|
|
3783
|
+
return this;
|
|
3784
|
+
}
|
|
3785
|
+
baseDirAt(pos, side) {
|
|
3786
|
+
let { line, spans } = this.bidiSpansAt(pos);
|
|
3787
|
+
let level = spans[BidiSpan.find(spans, pos - line.from, -1, side)].level;
|
|
3788
|
+
return level == this.baseDir;
|
|
3789
|
+
}
|
|
3790
|
+
dirAt(pos, side) {
|
|
3791
|
+
let { line, spans } = this.bidiSpansAt(pos);
|
|
3792
|
+
return spans[BidiSpan.find(spans, pos - line.from, -1, side)].dir;
|
|
3793
|
+
}
|
|
3794
|
+
// Used to short-circuit bidi tests for content with a uniform direction
|
|
3795
|
+
bidiIn(from, to) {
|
|
3796
|
+
let { spans, line } = this.bidiSpansAt(from);
|
|
3797
|
+
return spans.length > 1 || spans.length && (spans[0].level != this.baseDir || spans[0].to + line.from < to);
|
|
3798
|
+
}
|
|
3799
|
+
// Scan through the rectangles for the content of a tile with inline
|
|
3800
|
+
// content, looking for one that overlaps the queried position
|
|
3801
|
+
// vertically andis
|
|
3802
|
+
// closest horizontally. The caller is responsible for dividing its
|
|
3803
|
+
// content into N pieces, and pass an array with N+1 positions
|
|
3804
|
+
// (including the position after the last piece). For a text tile,
|
|
3805
|
+
// these will be character clusters, for a composite tile, these
|
|
3806
|
+
// will be child tiles.
|
|
3807
|
+
scan(positions, getRects) {
|
|
3808
|
+
let lo = 0, hi = positions.length - 1, seen = new Set();
|
|
3809
|
+
let bidi = this.bidiIn(positions[0], positions[hi]);
|
|
3810
|
+
let above, below;
|
|
3811
|
+
let closestI = -1, closestDx = 1e9, closestRect;
|
|
3812
|
+
// Because, when the content is bidirectional, a regular binary
|
|
3813
|
+
// search is hard to perform (the content order does not
|
|
3814
|
+
// correspond to visual order), this loop does something between a
|
|
3815
|
+
// regular binary search and a full scan, depending on what it can
|
|
3816
|
+
// get away with. The outer hi/lo bounds are only adjusted for
|
|
3817
|
+
// elements that are part of the base order.
|
|
3818
|
+
//
|
|
3819
|
+
// To make sure all elements inside those bounds are visited,
|
|
3820
|
+
// eventually, we keep a set of seen indices, and if the midpoint
|
|
3821
|
+
// has already been handled, we start in a random index within the
|
|
3822
|
+
// current bounds and scan forward until we find an index that
|
|
3823
|
+
// hasn't been seen yet.
|
|
3824
|
+
search: while (lo < hi) {
|
|
3825
|
+
let dist = hi - lo, mid = (lo + hi) >> 1;
|
|
3826
|
+
adjust: if (seen.has(mid)) {
|
|
3827
|
+
let scan = lo + Math.floor(Math.random() * dist);
|
|
3828
|
+
for (let i = 0; i < dist; i++) {
|
|
3829
|
+
if (!seen.has(scan)) {
|
|
3830
|
+
mid = scan;
|
|
3831
|
+
break adjust;
|
|
3832
|
+
}
|
|
3833
|
+
scan++;
|
|
3834
|
+
if (scan == hi)
|
|
3835
|
+
scan = lo; // Wrap around
|
|
3808
3836
|
}
|
|
3837
|
+
break search; // No index found, we're done
|
|
3809
3838
|
}
|
|
3839
|
+
seen.add(mid);
|
|
3840
|
+
let rects = getRects(mid);
|
|
3841
|
+
if (rects)
|
|
3842
|
+
for (let i = 0; i < rects.length; i++) {
|
|
3843
|
+
let rect = rects[i], side = 0;
|
|
3844
|
+
if (rect.bottom < this.y) {
|
|
3845
|
+
if (!above || above.bottom < rect.bottom)
|
|
3846
|
+
above = rect;
|
|
3847
|
+
side = 1;
|
|
3848
|
+
}
|
|
3849
|
+
else if (rect.top > this.y) {
|
|
3850
|
+
if (!below || below.top > rect.top)
|
|
3851
|
+
below = rect;
|
|
3852
|
+
side = -1;
|
|
3853
|
+
}
|
|
3854
|
+
else {
|
|
3855
|
+
let off = rect.left > this.x ? this.x - rect.left : rect.right < this.x ? this.x - rect.right : 0;
|
|
3856
|
+
let dx = Math.abs(off);
|
|
3857
|
+
if (dx < closestDx) {
|
|
3858
|
+
closestI = mid;
|
|
3859
|
+
closestDx = dx;
|
|
3860
|
+
closestRect = rect;
|
|
3861
|
+
}
|
|
3862
|
+
if (off)
|
|
3863
|
+
side = (off < 0) == (this.baseDir == Direction.LTR) ? -1 : 1;
|
|
3864
|
+
}
|
|
3865
|
+
// Narrow binary search when it is safe to do so
|
|
3866
|
+
if (side == -1 && (!bidi || this.baseDirAt(positions[mid], 1)))
|
|
3867
|
+
hi = mid;
|
|
3868
|
+
else if (side == 1 && (!bidi || this.baseDirAt(positions[mid + 1], -1)))
|
|
3869
|
+
lo = mid + 1;
|
|
3870
|
+
}
|
|
3810
3871
|
}
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
let
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
break;
|
|
3818
|
-
i = next;
|
|
3872
|
+
// If no element with y overlap is found, find the nearest element
|
|
3873
|
+
// on the y axis, move this.y into it, and retry the scan.
|
|
3874
|
+
if (!closestRect) {
|
|
3875
|
+
let side = above && (!below || (this.y - above.bottom < below.top - this.y)) ? above : below;
|
|
3876
|
+
this.y = (side.top + side.bottom) / 2;
|
|
3877
|
+
return this.scan(positions, getRects);
|
|
3819
3878
|
}
|
|
3820
|
-
let
|
|
3821
|
-
return
|
|
3879
|
+
let ltr = (bidi ? this.dirAt(positions[closestI], 1) : this.baseDir) == Direction.LTR;
|
|
3880
|
+
return {
|
|
3881
|
+
i: closestI,
|
|
3882
|
+
// Test whether x is closes to the start or end of this element
|
|
3883
|
+
after: (this.x > (closestRect.left + closestRect.right) / 2) == ltr
|
|
3884
|
+
};
|
|
3822
3885
|
}
|
|
3823
|
-
|
|
3886
|
+
scanText(tile, offset) {
|
|
3887
|
+
let positions = [];
|
|
3888
|
+
for (let i = 0; i < tile.length; i = findClusterBreak(tile.text, i))
|
|
3889
|
+
positions.push(offset + i);
|
|
3890
|
+
positions.push(offset + tile.length);
|
|
3891
|
+
let scan = this.scan(positions, i => {
|
|
3892
|
+
let off = positions[i] - offset, end = positions[i + 1] - offset;
|
|
3893
|
+
return textRange(tile.dom, off, end).getClientRects();
|
|
3894
|
+
});
|
|
3895
|
+
return scan.after ? new PosAssoc(positions[scan.i + 1], -1) : new PosAssoc(positions[scan.i], 1);
|
|
3896
|
+
}
|
|
3897
|
+
scanTile(tile, offset) {
|
|
3824
3898
|
if (!tile.length)
|
|
3825
3899
|
return new PosAssoc(offset, 1);
|
|
3826
|
-
|
|
3900
|
+
if (tile.children.length == 1) { // Short-circuit single-child tiles
|
|
3901
|
+
let child = tile.children[0];
|
|
3902
|
+
if (child.isText())
|
|
3903
|
+
return this.scanText(child, offset);
|
|
3904
|
+
else if (child.isComposite())
|
|
3905
|
+
return this.scanTile(child, offset);
|
|
3906
|
+
}
|
|
3907
|
+
let positions = [offset];
|
|
3908
|
+
for (let i = 0, pos = offset; i < tile.children.length; i++)
|
|
3909
|
+
positions.push(pos += tile.children[i].length);
|
|
3910
|
+
let scan = this.scan(positions, i => {
|
|
3827
3911
|
let child = tile.children[i];
|
|
3828
3912
|
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);
|
|
3913
|
+
return null;
|
|
3914
|
+
return (child.dom.nodeType == 1 ? child.dom : textRange(child.dom, 0, child.length)).getClientRects();
|
|
3915
|
+
});
|
|
3916
|
+
let child = tile.children[scan.i], pos = positions[scan.i];
|
|
3917
|
+
if (child.isText())
|
|
3918
|
+
return this.scanText(child, pos);
|
|
3919
|
+
if (child.isComposite())
|
|
3920
|
+
return this.scanTile(child, pos);
|
|
3921
|
+
return scan.after ? new PosAssoc(positions[scan.i + 1], -1) : new PosAssoc(pos, 1);
|
|
3840
3922
|
}
|
|
3841
3923
|
}
|
|
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
3924
|
|
|
3847
3925
|
const LineBreakPlaceholder = "\uffff";
|
|
3848
3926
|
class DOMReader {
|
|
@@ -4996,8 +5074,7 @@ handlers.copy = handlers.cut = (view, event) => {
|
|
|
4996
5074
|
// spans multiple elements including this CodeMirror. The copy event may
|
|
4997
5075
|
// bubble through CodeMirror (e.g. when CodeMirror is the first or the last
|
|
4998
5076
|
// element in the selection), but we should let the parent handle it.
|
|
4999
|
-
|
|
5000
|
-
if (domSel && !hasSelection(view.contentDOM, domSel))
|
|
5077
|
+
if (!hasSelection(view.contentDOM, view.observer.selectionRange))
|
|
5001
5078
|
return false;
|
|
5002
5079
|
let { text, ranges, linewise } = copiedRange(view.state);
|
|
5003
5080
|
if (!text && !linewise)
|