@codemirror/view 0.19.19 → 0.19.23

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/dist/index.js CHANGED
@@ -183,7 +183,7 @@ function scrollRectIntoView(dom, rect, side, center) {
183
183
  }
184
184
  }
185
185
  }
186
- class DOMSelection {
186
+ class DOMSelectionState {
187
187
  constructor() {
188
188
  this.anchorNode = null;
189
189
  this.anchorOffset = 0;
@@ -194,11 +194,14 @@ class DOMSelection {
194
194
  return this.anchorNode == domSel.anchorNode && this.anchorOffset == domSel.anchorOffset &&
195
195
  this.focusNode == domSel.focusNode && this.focusOffset == domSel.focusOffset;
196
196
  }
197
- set(domSel) {
198
- this.anchorNode = domSel.anchorNode;
199
- this.anchorOffset = domSel.anchorOffset;
200
- this.focusNode = domSel.focusNode;
201
- this.focusOffset = domSel.focusOffset;
197
+ setRange(range) {
198
+ this.set(range.anchorNode, range.anchorOffset, range.focusNode, range.focusOffset);
199
+ }
200
+ set(anchorNode, anchorOffset, focusNode, focusOffset) {
201
+ this.anchorNode = anchorNode;
202
+ this.anchorOffset = anchorOffset;
203
+ this.focusNode = focusNode;
204
+ this.focusOffset = focusOffset;
202
205
  }
203
206
  }
204
207
  let preventScrollSupported = null;
@@ -1905,7 +1908,7 @@ class ViewUpdate {
1905
1908
  Whether the document changed in this update.
1906
1909
  */
1907
1910
  get docChanged() {
1908
- return this.transactions.some(tr => tr.docChanged);
1911
+ return !this.changes.empty;
1909
1912
  }
1910
1913
  /**
1911
1914
  Whether the selection was explicitly set in this update.
@@ -1939,6 +1942,10 @@ class DocView extends ContentView {
1939
1942
  // we don't mess it up when reading it back it
1940
1943
  this.impreciseAnchor = null;
1941
1944
  this.impreciseHead = null;
1945
+ this.forceSelection = false;
1946
+ // Used by the resize observer to ignore resizes that we caused
1947
+ // ourselves
1948
+ this.lastUpdate = Date.now();
1942
1949
  this.setDOM(view.contentDOM);
1943
1950
  this.children = [new LineView];
1944
1951
  this.children[0].setParent(this);
@@ -1971,21 +1978,19 @@ class DocView extends ContentView {
1971
1978
  // getSelection than the one that it actually shows to the user.
1972
1979
  // This forces a selection update when lines are joined to work
1973
1980
  // around that. Issue #54
1974
- let forceSelection = (browser.ie || browser.chrome) && !this.compositionDeco.size && update &&
1975
- update.state.doc.lines != update.startState.doc.lines;
1981
+ if ((browser.ie || browser.chrome) && !this.compositionDeco.size && update &&
1982
+ update.state.doc.lines != update.startState.doc.lines)
1983
+ this.forceSelection = true;
1976
1984
  let prevDeco = this.decorations, deco = this.updateDeco();
1977
1985
  let decoDiff = findChangedDeco(prevDeco, deco, update.changes);
1978
1986
  changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
1979
- let pointerSel = update.transactions.some(tr => tr.isUserEvent("select.pointer"));
1980
- if (this.dirty == 0 /* Not */ && changedRanges.length == 0 &&
1981
- !(update.flags & 4 /* Viewport */) &&
1982
- update.state.selection.main.from >= this.view.viewport.from &&
1983
- update.state.selection.main.to <= this.view.viewport.to) {
1984
- this.updateSelection(forceSelection, pointerSel);
1987
+ if (this.dirty == 0 /* Not */ && changedRanges.length == 0) {
1985
1988
  return false;
1986
1989
  }
1987
1990
  else {
1988
- this.updateInner(changedRanges, deco, update.startState.doc.length, forceSelection, pointerSel);
1991
+ this.updateInner(changedRanges, deco, update.startState.doc.length);
1992
+ if (update.transactions.length)
1993
+ this.lastUpdate = Date.now();
1989
1994
  return true;
1990
1995
  }
1991
1996
  }
@@ -1993,13 +1998,16 @@ class DocView extends ContentView {
1993
1998
  if (this.dirty) {
1994
1999
  this.view.observer.ignore(() => this.view.docView.sync());
1995
2000
  this.dirty = 0 /* Not */;
2001
+ this.updateSelection(true);
1996
2002
  }
1997
- if (sel)
2003
+ else {
1998
2004
  this.updateSelection();
2005
+ }
1999
2006
  }
2000
2007
  // Used both by update and checkLayout do perform the actual DOM
2001
2008
  // update
2002
- updateInner(changes, deco, oldLength, forceSelection = false, pointerSel = false) {
2009
+ updateInner(changes, deco, oldLength) {
2010
+ this.view.viewState.mustMeasureContent = true;
2003
2011
  this.updateChildren(changes, deco, oldLength);
2004
2012
  let { observer } = this.view;
2005
2013
  observer.ignore(() => {
@@ -2007,7 +2015,7 @@ class DocView extends ContentView {
2007
2015
  // messes with the scroll position during DOM mutation (though
2008
2016
  // no relayout is triggered and I cannot imagine how it can
2009
2017
  // recompute the scroll position without a layout)
2010
- this.dom.style.height = this.view.viewState.domHeight + "px";
2018
+ this.dom.style.height = this.view.viewState.contentHeight + "px";
2011
2019
  this.dom.style.minWidth = this.minWidth ? this.minWidth + "px" : "";
2012
2020
  // Chrome will sometimes, when DOM mutations occur directly
2013
2021
  // around the selection, get confused and report a different
@@ -2017,8 +2025,7 @@ class DocView extends ContentView {
2017
2025
  this.sync(track);
2018
2026
  this.dirty = 0 /* Not */;
2019
2027
  if (track && (track.written || observer.selectionRange.focusNode != track.node))
2020
- forceSelection = true;
2021
- this.updateSelection(forceSelection, pointerSel);
2028
+ this.forceSelection = true;
2022
2029
  this.dom.style.height = "";
2023
2030
  });
2024
2031
  let gaps = [];
@@ -2104,10 +2111,14 @@ class DocView extends ContentView {
2104
2111
  this.replaceChildren(fromI, toI, content);
2105
2112
  }
2106
2113
  // Sync the DOM selection to this.state.selection
2107
- updateSelection(force = false, fromPointer = false) {
2114
+ updateSelection(mustRead = false, fromPointer = false) {
2115
+ if (mustRead)
2116
+ this.view.observer.readSelectionRange();
2108
2117
  if (!(fromPointer || this.mayControlSelection()) ||
2109
2118
  browser.ios && this.view.inputState.rapidCompositionStart)
2110
2119
  return;
2120
+ let force = this.forceSelection;
2121
+ this.forceSelection = false;
2111
2122
  let main = this.view.state.selection.main;
2112
2123
  // FIXME need to handle the case where the selection falls inside a block range
2113
2124
  let anchor = this.domAtPos(main.anchor);
@@ -2289,7 +2300,7 @@ class DocView extends ContentView {
2289
2300
  let next = i == vs.viewports.length ? null : vs.viewports[i];
2290
2301
  let end = next ? next.from - 1 : this.length;
2291
2302
  if (end > pos) {
2292
- let height = vs.lineAt(end, 0).bottom - vs.lineAt(pos, 0).top;
2303
+ let height = vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top;
2293
2304
  deco.push(Decoration.replace({ widget: new BlockGapWidget(height), block: true, inclusive: true }).range(pos, end));
2294
2305
  }
2295
2306
  if (!next)
@@ -2895,13 +2906,14 @@ function domPosInText(node, x, y) {
2895
2906
  }
2896
2907
  function posAtCoords(view, { x, y }, precise, bias = -1) {
2897
2908
  var _a;
2898
- let content = view.contentDOM.getBoundingClientRect(), block;
2909
+ let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
2899
2910
  let halfLine = view.defaultLineHeight / 2;
2911
+ let block, yOffset = y - docTop;
2900
2912
  for (let bounced = false;;) {
2901
- block = view.blockAtHeight(y, content.top);
2902
- if (block.top > y || block.bottom < y) {
2903
- bias = block.top > y ? -1 : 1;
2904
- y = Math.min(block.bottom - halfLine, Math.max(block.top + halfLine, y));
2913
+ block = view.elementAtHeight(yOffset);
2914
+ if (block.top > yOffset || block.bottom < yOffset) {
2915
+ bias = block.top > yOffset ? -1 : 1;
2916
+ yOffset = Math.min(block.bottom - halfLine, Math.max(block.top + halfLine, yOffset));
2905
2917
  if (bounced)
2906
2918
  return precise ? null : 0;
2907
2919
  else
@@ -2909,8 +2921,9 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
2909
2921
  }
2910
2922
  if (block.type == BlockType.Text)
2911
2923
  break;
2912
- y = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2924
+ yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2913
2925
  }
2926
+ y = docTop + yOffset;
2914
2927
  let lineStart = block.from;
2915
2928
  // Clip x to the viewport sides
2916
2929
  x = Math.max(content.left + 1, Math.min(content.right - 1, x));
@@ -3023,17 +3036,17 @@ function moveVertically(view, start, forward, distance) {
3023
3036
  return EditorSelection.cursor(startPos);
3024
3037
  let goal = start.goalColumn, startY;
3025
3038
  let rect = view.contentDOM.getBoundingClientRect();
3026
- let startCoords = view.coordsAtPos(startPos);
3039
+ let startCoords = view.coordsAtPos(startPos), docTop = view.documentTop;
3027
3040
  if (startCoords) {
3028
3041
  if (goal == null)
3029
3042
  goal = startCoords.left - rect.left;
3030
3043
  startY = dir < 0 ? startCoords.top : startCoords.bottom;
3031
3044
  }
3032
3045
  else {
3033
- let line = view.viewState.lineAt(startPos, view.dom.getBoundingClientRect().top);
3046
+ let line = view.viewState.lineBlockAt(startPos - docTop);
3034
3047
  if (goal == null)
3035
3048
  goal = Math.min(rect.right - rect.left, view.defaultCharacterWidth * (startPos - line.from));
3036
- startY = dir < 0 ? line.top : line.bottom;
3049
+ startY = (dir < 0 ? line.top : line.bottom) + docTop;
3037
3050
  }
3038
3051
  let resolvedGoal = rect.left + goal;
3039
3052
  let dist = distance !== null && distance !== void 0 ? distance : (view.defaultLineHeight >> 1);
@@ -3284,7 +3297,7 @@ class MouseSelection {
3284
3297
  this.extend = startEvent.shiftKey;
3285
3298
  this.multiple = view.state.facet(EditorState.allowMultipleSelections) && addsSelectionRange(view, startEvent);
3286
3299
  this.dragMove = dragMovesSelection(view, startEvent);
3287
- this.dragging = isInPrimarySelection(view, startEvent) ? null : false;
3300
+ this.dragging = isInPrimarySelection(view, startEvent) && getClickType(startEvent) == 1 ? null : false;
3288
3301
  // When clicking outside of the selection, immediately apply the
3289
3302
  // effect of starting the selection
3290
3303
  if (this.dragging === false) {
@@ -3505,7 +3518,7 @@ function basicMouseSelection(view, event) {
3505
3518
  let last = start, lastEvent = event;
3506
3519
  return {
3507
3520
  update(update) {
3508
- if (update.changes) {
3521
+ if (update.docChanged) {
3509
3522
  if (start)
3510
3523
  start.pos = update.changes.mapPos(start.pos);
3511
3524
  startSel = startSel.map(update.changes);
@@ -3769,7 +3782,10 @@ class HeightOracle {
3769
3782
  return lines * this.lineHeight;
3770
3783
  }
3771
3784
  setDoc(doc) { this.doc = doc; return this; }
3772
- mustRefresh(lineHeights, whiteSpace, direction) {
3785
+ mustRefreshForStyle(whiteSpace, direction) {
3786
+ return (wrappingWhiteSpace.indexOf(whiteSpace) > -1) != this.lineWrapping || this.direction != direction;
3787
+ }
3788
+ mustRefreshForHeights(lineHeights) {
3773
3789
  let newHeight = false;
3774
3790
  for (let i = 0; i < lineHeights.length; i++) {
3775
3791
  let h = lineHeights[i];
@@ -3781,7 +3797,7 @@ class HeightOracle {
3781
3797
  this.heightSamples[Math.floor(h * 10)] = true;
3782
3798
  }
3783
3799
  }
3784
- return newHeight || (wrappingWhiteSpace.indexOf(whiteSpace) > -1) != this.lineWrapping || this.direction != direction;
3800
+ return newHeight;
3785
3801
  }
3786
3802
  refresh(whiteSpace, direction, lineHeight, charWidth, lineLength, knownHeights) {
3787
3803
  let lineWrapping = wrappingWhiteSpace.indexOf(whiteSpace) > -1;
@@ -3835,7 +3851,8 @@ class BlockInfo {
3835
3851
  */
3836
3852
  length,
3837
3853
  /**
3838
- The top position of the element.
3854
+ The top position of the element (relative to the top of the
3855
+ document).
3839
3856
  */
3840
3857
  top,
3841
3858
  /**
@@ -3869,13 +3886,19 @@ class BlockInfo {
3869
3886
  .concat(Array.isArray(other.type) ? other.type : [other]);
3870
3887
  return new BlockInfo(this.from, this.length + other.length, this.top, this.height + other.height, detail);
3871
3888
  }
3889
+ /**
3890
+ FIXME remove on next breaking release @internal
3891
+ */
3892
+ moveY(offset) {
3893
+ return !offset ? this : new BlockInfo(this.from, this.length, this.top + offset, this.height, Array.isArray(this.type) ? this.type.map(b => b.moveY(offset)) : this.type);
3894
+ }
3872
3895
  }
3873
3896
  var QueryType = /*@__PURE__*/(function (QueryType) {
3874
3897
  QueryType[QueryType["ByPos"] = 0] = "ByPos";
3875
3898
  QueryType[QueryType["ByHeight"] = 1] = "ByHeight";
3876
3899
  QueryType[QueryType["ByPosNoHeight"] = 2] = "ByPosNoHeight";
3877
3900
  return QueryType})(QueryType || (QueryType = {}));
3878
- const Epsilon = 1e-4;
3901
+ const Epsilon = 1e-3;
3879
3902
  class HeightMap {
3880
3903
  constructor(length, // The number of characters covered
3881
3904
  height, // Height of this part of the document
@@ -4102,22 +4125,30 @@ class HeightMapGap extends HeightMap {
4102
4125
  // can't be widgets or collapsed ranges in those lines, because
4103
4126
  // they would already have been added to the heightmap (gaps
4104
4127
  // only contain plain text).
4105
- let nodes = [], pos = Math.max(offset, measured.from);
4128
+ let nodes = [], pos = Math.max(offset, measured.from), singleHeight = -1;
4129
+ let wasChanged = oracle.heightChanged;
4106
4130
  if (measured.from > offset)
4107
4131
  nodes.push(new HeightMapGap(measured.from - offset - 1).updateHeight(oracle, offset));
4108
4132
  while (pos <= end && measured.more) {
4109
4133
  let len = oracle.doc.lineAt(pos).length;
4110
4134
  if (nodes.length)
4111
4135
  nodes.push(null);
4112
- let line = new HeightMapText(len, measured.heights[measured.index++]);
4136
+ let height = measured.heights[measured.index++];
4137
+ if (singleHeight == -1)
4138
+ singleHeight = height;
4139
+ else if (Math.abs(height - singleHeight) >= Epsilon)
4140
+ singleHeight = -2;
4141
+ let line = new HeightMapText(len, height);
4113
4142
  line.outdated = false;
4114
4143
  nodes.push(line);
4115
4144
  pos += len + 1;
4116
4145
  }
4117
4146
  if (pos <= end)
4118
4147
  nodes.push(null, new HeightMapGap(end - pos).updateHeight(oracle, pos));
4119
- oracle.heightChanged = true;
4120
- return HeightMap.of(nodes);
4148
+ let result = HeightMap.of(nodes);
4149
+ oracle.heightChanged = wasChanged || singleHeight < 0 || Math.abs(result.height - this.height) >= Epsilon ||
4150
+ Math.abs(singleHeight - this.lines(oracle.doc, offset).lineHeight) >= Epsilon;
4151
+ return result;
4121
4152
  }
4122
4153
  else if (force || this.outdated) {
4123
4154
  this.setHeight(oracle, oracle.heightForGap(offset, offset + this.length));
@@ -4475,13 +4506,18 @@ class ViewState {
4475
4506
  this.inView = true;
4476
4507
  this.paddingTop = 0;
4477
4508
  this.paddingBottom = 0;
4478
- this.contentWidth = 0;
4509
+ this.contentDOMWidth = 0;
4510
+ this.contentDOMHeight = 0;
4511
+ this.editorHeight = 0;
4479
4512
  this.heightOracle = new HeightOracle;
4480
4513
  // See VP.MaxDOMHeight
4481
4514
  this.scaler = IdScaler;
4482
4515
  this.scrollTarget = null;
4483
4516
  // Briefly set to true when printing, to disable viewport limiting
4484
4517
  this.printing = false;
4518
+ // Flag set when editor content was redrawn, so that the next
4519
+ // measure stage knows it must read DOM layout
4520
+ this.mustMeasureContent = true;
4485
4521
  this.visibleRanges = [];
4486
4522
  // Cursor 'assoc' is only significant when the cursor is on a line
4487
4523
  // wrap point, where it must stick to the character that it is
@@ -4494,6 +4530,7 @@ class ViewState {
4494
4530
  this.mustEnforceCursorAssoc = false;
4495
4531
  this.heightMap = HeightMap.empty().applyChanges(state.facet(decorations), Text.empty, this.heightOracle.setDoc(state.doc), [new ChangedRange(0, 0, 0, state.doc.length)]);
4496
4532
  this.viewport = this.getViewport(0, null);
4533
+ this.updateViewportLines();
4497
4534
  this.updateForViewport();
4498
4535
  this.lineGaps = this.ensureLineGaps([]);
4499
4536
  this.lineGapDeco = Decoration.set(this.lineGaps.map(gap => gap.draw(false)));
@@ -4504,7 +4541,7 @@ class ViewState {
4504
4541
  for (let i = 0; i <= 1; i++) {
4505
4542
  let pos = i ? main.head : main.anchor;
4506
4543
  if (!viewports.some(({ from, to }) => pos >= from && pos <= to)) {
4507
- let { from, to } = this.lineAt(pos, 0);
4544
+ let { from, to } = this.lineBlockAt(pos);
4508
4545
  viewports.push(new Viewport(from, to));
4509
4546
  }
4510
4547
  }
@@ -4512,6 +4549,12 @@ class ViewState {
4512
4549
  this.scaler = this.heightMap.height <= 7000000 /* MaxDOMHeight */ ? IdScaler :
4513
4550
  new BigScaler(this.heightOracle.doc, this.heightMap, this.viewports);
4514
4551
  }
4552
+ updateViewportLines() {
4553
+ this.viewportLines = [];
4554
+ this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.state.doc, 0, 0, block => {
4555
+ this.viewportLines.push(this.scaler.scale == 1 ? block : scaleBlock(block, this.scaler));
4556
+ });
4557
+ }
4515
4558
  update(update, scrollTarget = null) {
4516
4559
  let prev = this.state;
4517
4560
  this.state = update.state;
@@ -4526,7 +4569,11 @@ class ViewState {
4526
4569
  if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
4527
4570
  !this.viewportIsAppropriate(viewport))
4528
4571
  viewport = this.getViewport(0, scrollTarget);
4572
+ let updateLines = !update.changes.empty || (update.flags & 2 /* Height */) ||
4573
+ viewport.from != this.viewport.from || viewport.to != this.viewport.to;
4529
4574
  this.viewport = viewport;
4575
+ if (updateLines)
4576
+ this.updateViewportLines();
4530
4577
  this.updateForViewport();
4531
4578
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4532
4579
  this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
@@ -4537,13 +4584,17 @@ class ViewState {
4537
4584
  update.state.selection.main.empty && update.state.selection.main.assoc)
4538
4585
  this.mustEnforceCursorAssoc = true;
4539
4586
  }
4540
- measure(docView, repeated) {
4541
- let dom = docView.dom, whiteSpace = "", direction = Direction.LTR;
4542
- let result = 0;
4543
- if (!repeated) {
4587
+ measure(view) {
4588
+ let dom = view.contentDOM, style = window.getComputedStyle(dom);
4589
+ let oracle = this.heightOracle;
4590
+ let whiteSpace = style.whiteSpace, direction = style.direction == "rtl" ? Direction.RTL : Direction.LTR;
4591
+ let refresh = this.heightOracle.mustRefreshForStyle(whiteSpace, direction);
4592
+ let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != dom.clientHeight;
4593
+ let result = 0, bias = 0;
4594
+ if (measureContent) {
4595
+ this.mustMeasureContent = false;
4596
+ this.contentDOMHeight = dom.clientHeight;
4544
4597
  // Vertical padding
4545
- let style = window.getComputedStyle(dom);
4546
- whiteSpace = style.whiteSpace, direction = (style.direction == "rtl" ? Direction.RTL : Direction.LTR);
4547
4598
  let paddingTop = parseInt(style.paddingTop) || 0, paddingBottom = parseInt(style.paddingBottom) || 0;
4548
4599
  if (this.paddingTop != paddingTop || this.paddingBottom != paddingBottom) {
4549
4600
  result |= 8 /* Geometry */;
@@ -4558,35 +4609,42 @@ class ViewState {
4558
4609
  this.inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
4559
4610
  if (!this.inView)
4560
4611
  return 0;
4561
- let lineHeights = docView.measureVisibleLineHeights();
4562
- let refresh = false, bias = 0, oracle = this.heightOracle;
4563
- if (!repeated) {
4564
- let contentWidth = docView.dom.clientWidth;
4565
- if (oracle.mustRefresh(lineHeights, whiteSpace, direction) ||
4566
- oracle.lineWrapping && Math.abs(contentWidth - this.contentWidth) > oracle.charWidth) {
4567
- let { lineHeight, charWidth } = docView.measureTextSize();
4612
+ if (measureContent) {
4613
+ let lineHeights = view.docView.measureVisibleLineHeights();
4614
+ if (oracle.mustRefreshForHeights(lineHeights))
4615
+ refresh = true;
4616
+ let contentWidth = dom.clientWidth;
4617
+ if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
4618
+ let { lineHeight, charWidth } = view.docView.measureTextSize();
4568
4619
  refresh = oracle.refresh(whiteSpace, direction, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
4569
4620
  if (refresh) {
4570
- docView.minWidth = 0;
4621
+ view.docView.minWidth = 0;
4571
4622
  result |= 8 /* Geometry */;
4572
4623
  }
4573
4624
  }
4574
- if (this.contentWidth != contentWidth) {
4575
- this.contentWidth = contentWidth;
4625
+ if (this.contentDOMWidth != contentWidth) {
4626
+ this.contentDOMWidth = contentWidth;
4627
+ result |= 8 /* Geometry */;
4628
+ }
4629
+ if (this.editorHeight != view.scrollDOM.clientHeight) {
4630
+ this.editorHeight = view.scrollDOM.clientHeight;
4576
4631
  result |= 8 /* Geometry */;
4577
4632
  }
4578
4633
  if (dTop > 0 && dBottom > 0)
4579
4634
  bias = Math.max(dTop, dBottom);
4580
4635
  else if (dTop < 0 && dBottom < 0)
4581
4636
  bias = Math.min(dTop, dBottom);
4582
- }
4583
- oracle.heightChanged = false;
4584
- this.heightMap = this.heightMap.updateHeight(oracle, 0, refresh, new MeasuredHeights(this.viewport.from, lineHeights));
4585
- if (oracle.heightChanged)
4586
- result |= 2 /* Height */;
4587
- if (!this.viewportIsAppropriate(this.viewport, bias) ||
4588
- this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to))
4637
+ oracle.heightChanged = false;
4638
+ this.heightMap = this.heightMap.updateHeight(oracle, 0, refresh, new MeasuredHeights(this.viewport.from, lineHeights));
4639
+ if (oracle.heightChanged)
4640
+ result |= 2 /* Height */;
4641
+ }
4642
+ let viewportChange = !this.viewportIsAppropriate(this.viewport, bias) ||
4643
+ this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to);
4644
+ if (viewportChange)
4589
4645
  this.viewport = this.getViewport(bias, this.scrollTarget);
4646
+ if ((result & 2 /* Height */) || viewportChange)
4647
+ this.updateViewportLines();
4590
4648
  this.updateForViewport();
4591
4649
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4592
4650
  this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
@@ -4597,12 +4655,12 @@ class ViewState {
4597
4655
  // to a line end is going to trigger a layout anyway, so it
4598
4656
  // can't be a pure write. It should be rare that it does any
4599
4657
  // writing.
4600
- docView.enforceCursorAssoc();
4658
+ view.docView.enforceCursorAssoc();
4601
4659
  }
4602
4660
  return result;
4603
4661
  }
4604
- get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top, 0); }
4605
- get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom, 0); }
4662
+ get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top); }
4663
+ get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom); }
4606
4664
  getViewport(bias, scrollTarget) {
4607
4665
  // This will divide VP.Margin between the top and the
4608
4666
  // bottom, depending on the bias (the change in viewport position
@@ -4662,12 +4720,12 @@ class ViewState {
4662
4720
  // This won't work at all in predominantly right-to-left text.
4663
4721
  if (this.heightOracle.direction != Direction.LTR)
4664
4722
  return gaps;
4665
- this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.state.doc, 0, 0, line => {
4723
+ for (let line of this.viewportLines) {
4666
4724
  if (line.length < 4000 /* DoubleMargin */)
4667
- return;
4725
+ continue;
4668
4726
  let structure = lineStructure(line.from, line.to, this.state);
4669
4727
  if (structure.total < 4000 /* DoubleMargin */)
4670
- return;
4728
+ continue;
4671
4729
  let viewFrom, viewTo;
4672
4730
  if (this.heightOracle.lineWrapping) {
4673
4731
  let marginHeight = (2000 /* Margin */ / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
@@ -4697,7 +4755,7 @@ class ViewState {
4697
4755
  Math.abs(gap.from - from) < 1000 /* HalfMargin */ && Math.abs(gap.to - to) < 1000 /* HalfMargin */) ||
4698
4756
  new LineGap(from, to, this.gapSize(line, from, to, structure)));
4699
4757
  }
4700
- });
4758
+ }
4701
4759
  return gaps;
4702
4760
  }
4703
4761
  gapSize(line, from, to, structure) {
@@ -4729,27 +4787,18 @@ class ViewState {
4729
4787
  this.visibleRanges = ranges;
4730
4788
  return changed ? 4 /* Viewport */ : 0;
4731
4789
  }
4732
- lineAt(pos, editorTop) {
4733
- editorTop += this.paddingTop;
4734
- return scaleBlock(this.heightMap.lineAt(pos, QueryType.ByPos, this.state.doc, editorTop, 0), this.scaler, editorTop);
4735
- }
4736
- lineAtHeight(height, editorTop) {
4737
- editorTop += this.paddingTop;
4738
- return scaleBlock(this.heightMap.lineAt(this.scaler.fromDOM(height, editorTop), QueryType.ByHeight, this.state.doc, editorTop, 0), this.scaler, editorTop);
4790
+ lineBlockAt(pos) {
4791
+ return (pos >= this.viewport.from && pos <= this.viewport.to && this.viewportLines.find(b => b.from <= pos && b.to <= pos)) ||
4792
+ scaleBlock(this.heightMap.lineAt(pos, QueryType.ByPos, this.state.doc, 0, 0), this.scaler);
4739
4793
  }
4740
- blockAtHeight(height, editorTop) {
4741
- editorTop += this.paddingTop;
4742
- return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height, editorTop), this.state.doc, editorTop, 0), this.scaler, editorTop);
4794
+ lineBlockAtHeight(height) {
4795
+ return scaleBlock(this.heightMap.lineAt(this.scaler.fromDOM(height), QueryType.ByHeight, this.state.doc, 0, 0), this.scaler);
4743
4796
  }
4744
- forEachLine(from, to, f, editorTop) {
4745
- editorTop += this.paddingTop;
4746
- return this.heightMap.forEachLine(from, to, this.state.doc, editorTop, 0, this.scaler.scale == 1 ? f : b => f(scaleBlock(b, this.scaler, editorTop)));
4797
+ elementAtHeight(height) {
4798
+ return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height), this.state.doc, 0, 0), this.scaler);
4747
4799
  }
4748
4800
  get contentHeight() {
4749
- return this.domHeight + this.paddingTop + this.paddingBottom;
4750
- }
4751
- get domHeight() {
4752
- return this.scaler.toDOM(this.heightMap.height, this.paddingTop);
4801
+ return this.scaler.toDOM(this.heightMap.height) + this.paddingTop + this.paddingBottom;
4753
4802
  }
4754
4803
  }
4755
4804
  class Viewport {
@@ -4846,36 +4895,34 @@ class BigScaler {
4846
4895
  base = obj.bottom;
4847
4896
  }
4848
4897
  }
4849
- toDOM(n, top) {
4850
- n -= top;
4898
+ toDOM(n) {
4851
4899
  for (let i = 0, base = 0, domBase = 0;; i++) {
4852
4900
  let vp = i < this.viewports.length ? this.viewports[i] : null;
4853
4901
  if (!vp || n < vp.top)
4854
- return domBase + (n - base) * this.scale + top;
4902
+ return domBase + (n - base) * this.scale;
4855
4903
  if (n <= vp.bottom)
4856
- return vp.domTop + (n - vp.top) + top;
4904
+ return vp.domTop + (n - vp.top);
4857
4905
  base = vp.bottom;
4858
4906
  domBase = vp.domBottom;
4859
4907
  }
4860
4908
  }
4861
- fromDOM(n, top) {
4862
- n -= top;
4909
+ fromDOM(n) {
4863
4910
  for (let i = 0, base = 0, domBase = 0;; i++) {
4864
4911
  let vp = i < this.viewports.length ? this.viewports[i] : null;
4865
4912
  if (!vp || n < vp.domTop)
4866
- return base + (n - domBase) / this.scale + top;
4913
+ return base + (n - domBase) / this.scale;
4867
4914
  if (n <= vp.domBottom)
4868
- return vp.top + (n - vp.domTop) + top;
4915
+ return vp.top + (n - vp.domTop);
4869
4916
  base = vp.bottom;
4870
4917
  domBase = vp.domBottom;
4871
4918
  }
4872
4919
  }
4873
4920
  }
4874
- function scaleBlock(block, scaler, top) {
4921
+ function scaleBlock(block, scaler) {
4875
4922
  if (scaler.scale == 1)
4876
4923
  return block;
4877
- let bTop = scaler.toDOM(block.top, top), bBottom = scaler.toDOM(block.bottom, top);
4878
- return new BlockInfo(block.from, block.length, bTop, bBottom - bTop, Array.isArray(block.type) ? block.type.map(b => scaleBlock(b, scaler, top)) : block.type);
4924
+ let bTop = scaler.toDOM(block.top), bBottom = scaler.toDOM(block.bottom);
4925
+ return new BlockInfo(block.from, block.length, bTop, bBottom - bTop, Array.isArray(block.type) ? block.type.map(b => scaleBlock(b, scaler)) : block.type);
4879
4926
  }
4880
4927
 
4881
4928
  const theme = /*@__PURE__*/Facet.define({ combine: strs => strs.join(" ") });
@@ -5060,24 +5107,30 @@ class DOMObserver {
5060
5107
  this.onChange = onChange;
5061
5108
  this.onScrollChanged = onScrollChanged;
5062
5109
  this.active = false;
5063
- this.ignoreSelection = new DOMSelection;
5110
+ // The known selection. Kept in our own object, as opposed to just
5111
+ // directly accessing the selection because:
5112
+ // - Safari doesn't report the right selection in shadow DOM
5113
+ // - Reading from the selection forces a DOM layout
5114
+ // - This way, we can ignore selectionchange events if we have
5115
+ // already seen the 'new' selection
5116
+ this.selectionRange = new DOMSelectionState;
5117
+ // Set when a selection change is detected, cleared on flush
5118
+ this.selectionChanged = false;
5064
5119
  this.delayedFlush = -1;
5120
+ this.resizeTimeout = -1;
5065
5121
  this.queue = [];
5066
- this.lastFlush = 0;
5067
5122
  this.scrollTargets = [];
5068
5123
  this.intersection = null;
5124
+ this.resize = null;
5069
5125
  this.intersecting = false;
5070
5126
  this.gapIntersection = null;
5071
5127
  this.gaps = [];
5072
- // Used to work around a Safari Selection/shadow DOM bug (#414)
5073
- this._selectionRange = null;
5074
5128
  // Timeout for scheduling check of the parents that need scroll handlers
5075
5129
  this.parentCheck = -1;
5076
5130
  this.dom = view.contentDOM;
5077
5131
  this.observer = new MutationObserver(mutations => {
5078
5132
  for (let mut of mutations)
5079
5133
  this.queue.push(mut);
5080
- this._selectionRange = null;
5081
5134
  // IE11 will sometimes (on typing over a selection or
5082
5135
  // backspacing out a single character text node) call the
5083
5136
  // observer callback before actually updating the DOM.
@@ -5102,6 +5155,16 @@ class DOMObserver {
5102
5155
  this.flushSoon();
5103
5156
  };
5104
5157
  this.onSelectionChange = this.onSelectionChange.bind(this);
5158
+ if (typeof ResizeObserver == "function") {
5159
+ this.resize = new ResizeObserver(() => {
5160
+ if (this.view.docView.lastUpdate < Date.now() - 75 && this.resizeTimeout < 0)
5161
+ this.resizeTimeout = setTimeout(() => {
5162
+ this.resizeTimeout = -1;
5163
+ this.view.requestMeasure();
5164
+ }, 50);
5165
+ });
5166
+ this.resize.observe(view.scrollDOM);
5167
+ }
5105
5168
  this.start();
5106
5169
  this.onScroll = this.onScroll.bind(this);
5107
5170
  window.addEventListener("scroll", this.onScroll);
@@ -5122,10 +5185,12 @@ class DOMObserver {
5122
5185
  }, {});
5123
5186
  }
5124
5187
  this.listenForScroll();
5188
+ this.readSelectionRange();
5189
+ this.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange);
5125
5190
  }
5126
5191
  onScroll(e) {
5127
5192
  if (this.intersecting)
5128
- this.flush();
5193
+ this.flush(false);
5129
5194
  this.onScrollChanged(e);
5130
5195
  }
5131
5196
  updateGaps(gaps) {
@@ -5137,8 +5202,8 @@ class DOMObserver {
5137
5202
  }
5138
5203
  }
5139
5204
  onSelectionChange(event) {
5140
- if (this.lastFlush < Date.now() - 50)
5141
- this._selectionRange = null;
5205
+ if (!this.readSelectionRange())
5206
+ return;
5142
5207
  let { view } = this, sel = this.selectionRange;
5143
5208
  if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
5144
5209
  return;
@@ -5153,24 +5218,22 @@ class DOMObserver {
5153
5218
  sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset))
5154
5219
  this.flushSoon();
5155
5220
  else
5156
- this.flush();
5157
- }
5158
- get selectionRange() {
5159
- if (!this._selectionRange) {
5160
- let { root } = this.view, sel = getSelection(root);
5161
- // The Selection object is broken in shadow roots in Safari. See
5162
- // https://github.com/codemirror/codemirror.next/issues/414
5163
- if (browser.safari && root.nodeType == 11 && deepActiveElement() == this.view.contentDOM)
5164
- sel = safariSelectionRangeHack(this.view) || sel;
5165
- this._selectionRange = sel;
5166
- }
5167
- return this._selectionRange;
5221
+ this.flush(false);
5222
+ }
5223
+ readSelectionRange() {
5224
+ let { root } = this.view, domSel = getSelection(root);
5225
+ // The Selection object is broken in shadow roots in Safari. See
5226
+ // https://github.com/codemirror/codemirror.next/issues/414
5227
+ let range = browser.safari && root.nodeType == 11 && deepActiveElement() == this.view.contentDOM &&
5228
+ safariSelectionRangeHack(this.view) || domSel;
5229
+ if (this.selectionRange.eq(range))
5230
+ return false;
5231
+ this.selectionRange.setRange(range);
5232
+ return this.selectionChanged = true;
5168
5233
  }
5169
5234
  setSelectionRange(anchor, head) {
5170
- var _a;
5171
- if (!((_a = this._selectionRange) === null || _a === void 0 ? void 0 : _a.type))
5172
- this._selectionRange = { anchorNode: anchor.node, anchorOffset: anchor.offset,
5173
- focusNode: head.node, focusOffset: head.offset };
5235
+ this.selectionRange.set(anchor.node, anchor.offset, head.node, head.offset);
5236
+ this.selectionChanged = false;
5174
5237
  }
5175
5238
  listenForScroll() {
5176
5239
  this.parentCheck = -1;
@@ -5217,7 +5280,6 @@ class DOMObserver {
5217
5280
  if (this.active)
5218
5281
  return;
5219
5282
  this.observer.observe(this.dom, observeOptions);
5220
- this.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange);
5221
5283
  if (useCharData)
5222
5284
  this.dom.addEventListener("DOMCharacterDataModified", this.onCharData);
5223
5285
  this.active = true;
@@ -5227,18 +5289,14 @@ class DOMObserver {
5227
5289
  return;
5228
5290
  this.active = false;
5229
5291
  this.observer.disconnect();
5230
- this.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
5231
5292
  if (useCharData)
5232
5293
  this.dom.removeEventListener("DOMCharacterDataModified", this.onCharData);
5233
5294
  }
5234
- clearSelection() {
5235
- this.ignoreSelection.set(this.selectionRange);
5236
- }
5237
5295
  // Throw away any pending changes
5238
5296
  clear() {
5239
5297
  this.observer.takeRecords();
5240
5298
  this.queue.length = 0;
5241
- this.clearSelection();
5299
+ this.selectionChanged = false;
5242
5300
  }
5243
5301
  flushSoon() {
5244
5302
  if (this.delayedFlush < 0)
@@ -5275,24 +5333,24 @@ class DOMObserver {
5275
5333
  return { from, to, typeOver };
5276
5334
  }
5277
5335
  // Apply pending changes, if any
5278
- flush() {
5336
+ flush(readSelection = true) {
5337
+ if (readSelection)
5338
+ this.readSelectionRange();
5279
5339
  // Completely hold off flushing when pending keys are set—the code
5280
5340
  // managing those will make sure processRecords is called and the
5281
5341
  // view is resynchronized after
5282
5342
  if (this.delayedFlush >= 0 || this.view.inputState.pendingAndroidKey)
5283
5343
  return;
5284
- this.lastFlush = Date.now();
5285
5344
  let { from, to, typeOver } = this.processRecords();
5286
- let selection = this.selectionRange;
5287
- let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
5345
+ let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5288
5346
  if (from < 0 && !newSel)
5289
5347
  return;
5348
+ this.selectionChanged = false;
5290
5349
  let startState = this.view.state;
5291
5350
  this.onChange(from, to, typeOver);
5292
5351
  // The view wasn't updated
5293
5352
  if (this.view.state == startState)
5294
5353
  this.view.docView.reset(newSel);
5295
- this.clearSelection();
5296
5354
  }
5297
5355
  readMutation(rec) {
5298
5356
  let cView = this.view.docView.nearest(rec.target);
@@ -5315,15 +5373,16 @@ class DOMObserver {
5315
5373
  }
5316
5374
  }
5317
5375
  destroy() {
5376
+ var _a, _b, _c;
5318
5377
  this.stop();
5319
- if (this.intersection)
5320
- this.intersection.disconnect();
5321
- if (this.gapIntersection)
5322
- this.gapIntersection.disconnect();
5378
+ (_a = this.intersection) === null || _a === void 0 ? void 0 : _a.disconnect();
5379
+ (_b = this.gapIntersection) === null || _b === void 0 ? void 0 : _b.disconnect();
5380
+ (_c = this.resize) === null || _c === void 0 ? void 0 : _c.disconnect();
5323
5381
  for (let dom of this.scrollTargets)
5324
5382
  dom.removeEventListener("scroll", this.onScroll);
5325
5383
  window.removeEventListener("scroll", this.onScroll);
5326
5384
  clearTimeout(this.parentCheck);
5385
+ clearTimeout(this.resizeTimeout);
5327
5386
  }
5328
5387
  }
5329
5388
  function findChild(cView, dom, dir) {
@@ -5336,6 +5395,7 @@ function findChild(cView, dom, dir) {
5336
5395
  }
5337
5396
  return null;
5338
5397
  }
5398
+ // Used to work around a Safari Selection/shadow DOM bug (#414)
5339
5399
  function safariSelectionRangeHack(view) {
5340
5400
  let found = null;
5341
5401
  // Because Safari (at least in 2018-2021) doesn't provide regular
@@ -5755,6 +5815,7 @@ class EditorView {
5755
5815
  this.mountStyles();
5756
5816
  this.updateAttrs();
5757
5817
  this.showAnnouncements(transactions);
5818
+ this.docView.updateSelection(redrawn, transactions.some(tr => tr.isUserEvent("select.pointer")));
5758
5819
  }
5759
5820
  finally {
5760
5821
  this.updateState = 0 /* Idle */;
@@ -5832,7 +5893,7 @@ class EditorView {
5832
5893
  return;
5833
5894
  if (this.measureScheduled > -1)
5834
5895
  cancelAnimationFrame(this.measureScheduled);
5835
- this.measureScheduled = -1; // Prevent requestMeasure calls from scheduling another animation frame
5896
+ this.measureScheduled = 0; // Prevent requestMeasure calls from scheduling another animation frame
5836
5897
  if (flush)
5837
5898
  this.observer.flush();
5838
5899
  let updated = null;
@@ -5840,11 +5901,11 @@ class EditorView {
5840
5901
  for (let i = 0;; i++) {
5841
5902
  this.updateState = 1 /* Measuring */;
5842
5903
  let oldViewport = this.viewport;
5843
- let changed = this.viewState.measure(this.docView, i > 0);
5904
+ let changed = this.viewState.measure(this);
5844
5905
  if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
5845
5906
  break;
5846
5907
  if (i > 5) {
5847
- console.warn("Viewport failed to stabilize");
5908
+ console.warn(this.measureRequests.length ? "Measure loop restarted more than 5 times" : "Viewport failed to stabilize");
5848
5909
  break;
5849
5910
  }
5850
5911
  let measuring = [];
@@ -5860,7 +5921,7 @@ class EditorView {
5860
5921
  return BadMeasure;
5861
5922
  }
5862
5923
  });
5863
- let update = new ViewUpdate(this, this.state);
5924
+ let update = new ViewUpdate(this, this.state), redrawn = false;
5864
5925
  update.flags |= changed;
5865
5926
  if (!updated)
5866
5927
  updated = update;
@@ -5870,14 +5931,15 @@ class EditorView {
5870
5931
  if (!update.empty) {
5871
5932
  this.updatePlugins(update);
5872
5933
  this.inputState.update(update);
5934
+ this.updateAttrs();
5935
+ redrawn = this.docView.update(update);
5873
5936
  }
5874
- this.updateAttrs();
5875
- if (changed)
5876
- this.docView.update(update);
5877
5937
  for (let i = 0; i < measuring.length; i++)
5878
5938
  if (measured[i] != BadMeasure) {
5879
5939
  try {
5880
- measuring[i].write(measured[i], this);
5940
+ let m = measuring[i];
5941
+ if (m.write)
5942
+ m.write(measured[i], this);
5881
5943
  }
5882
5944
  catch (e) {
5883
5945
  logException(this.state, e);
@@ -5887,14 +5949,16 @@ class EditorView {
5887
5949
  this.docView.scrollIntoView(this.viewState.scrollTarget);
5888
5950
  this.viewState.scrollTarget = null;
5889
5951
  }
5952
+ if (redrawn)
5953
+ this.docView.updateSelection(true);
5890
5954
  if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
5891
5955
  break;
5892
5956
  }
5893
5957
  }
5894
5958
  finally {
5895
5959
  this.updateState = 0 /* Idle */;
5960
+ this.measureScheduled = -1;
5896
5961
  }
5897
- this.measureScheduled = -1;
5898
5962
  if (updated && !updated.empty)
5899
5963
  for (let listener of this.state.facet(updateListener))
5900
5964
  listener(updated);
@@ -5911,8 +5975,6 @@ class EditorView {
5911
5975
  let editorAttrs = combineAttrs(this.state.facet(editorAttributes), {
5912
5976
  class: "cm-editor" + (this.hasFocus ? " cm-focused " : " ") + this.themeClasses
5913
5977
  });
5914
- updateAttrs(this.dom, this.editorAttrs, editorAttrs);
5915
- this.editorAttrs = editorAttrs;
5916
5978
  let contentAttrs = {
5917
5979
  spellcheck: "false",
5918
5980
  autocorrect: "off",
@@ -5927,7 +5989,11 @@ class EditorView {
5927
5989
  if (this.state.readOnly)
5928
5990
  contentAttrs["aria-readonly"] = "true";
5929
5991
  combineAttrs(this.state.facet(contentAttributes), contentAttrs);
5930
- updateAttrs(this.contentDOM, this.contentAttrs, contentAttrs);
5992
+ this.observer.ignore(() => {
5993
+ updateAttrs(this.contentDOM, this.contentAttrs, contentAttrs);
5994
+ updateAttrs(this.dom, this.editorAttrs, editorAttrs);
5995
+ });
5996
+ this.editorAttrs = editorAttrs;
5931
5997
  this.contentAttrs = contentAttrs;
5932
5998
  }
5933
5999
  showAnnouncements(trs) {
@@ -5997,6 +6063,20 @@ class EditorView {
5997
6063
  return null;
5998
6064
  }
5999
6065
  /**
6066
+ The top position of the document, in screen coordinates. This
6067
+ may be negative when the editor is scrolled down. Points
6068
+ directly to the top of the first line, not above the padding.
6069
+ */
6070
+ get documentTop() {
6071
+ return this.contentDOM.getBoundingClientRect().top + this.viewState.paddingTop;
6072
+ }
6073
+ /**
6074
+ Reports the padding above and below the document.
6075
+ */
6076
+ get documentPadding() {
6077
+ return { top: this.viewState.paddingTop, bottom: this.viewState.paddingBottom };
6078
+ }
6079
+ /**
6000
6080
  Find the line or block widget at the given vertical position.
6001
6081
 
6002
6082
  By default, this position is interpreted as a screen position,
@@ -6006,10 +6086,21 @@ class EditorView {
6006
6086
  position, or a precomputed document top
6007
6087
  (`view.contentDOM.getBoundingClientRect().top`) to limit layout
6008
6088
  queries.
6089
+
6090
+ *Deprecated: use `blockAtHeight` instead.*
6009
6091
  */
6010
6092
  blockAtHeight(height, docTop) {
6093
+ let top = ensureTop(docTop, this);
6094
+ return this.elementAtHeight(height - top).moveY(top);
6095
+ }
6096
+ /**
6097
+ Find the text line or block widget at the given vertical
6098
+ position (which is interpreted as relative to the [top of the
6099
+ document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop)
6100
+ */
6101
+ elementAtHeight(height) {
6011
6102
  this.readMeasured();
6012
- return this.viewState.blockAtHeight(height, ensureTop(docTop, this.contentDOM));
6103
+ return this.viewState.elementAtHeight(height);
6013
6104
  }
6014
6105
  /**
6015
6106
  Find information for the visual line (see
@@ -6021,20 +6112,43 @@ class EditorView {
6021
6112
  Defaults to treating `height` as a screen position. See
6022
6113
  [`blockAtHeight`](https://codemirror.net/6/docs/ref/#view.EditorView.blockAtHeight) for the
6023
6114
  interpretation of the `docTop` parameter.
6115
+
6116
+ *Deprecated: use `lineBlockAtHeight` instead.*
6024
6117
  */
6025
6118
  visualLineAtHeight(height, docTop) {
6119
+ let top = ensureTop(docTop, this);
6120
+ return this.lineBlockAtHeight(height - top).moveY(top);
6121
+ }
6122
+ /**
6123
+ Find the line block (see
6124
+ [`lineBlockAt`](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt) at the given
6125
+ height.
6126
+ */
6127
+ lineBlockAtHeight(height) {
6026
6128
  this.readMeasured();
6027
- return this.viewState.lineAtHeight(height, ensureTop(docTop, this.contentDOM));
6129
+ return this.viewState.lineBlockAtHeight(height);
6028
6130
  }
6029
6131
  /**
6030
6132
  Iterate over the height information of the visual lines in the
6031
6133
  viewport. The heights of lines are reported relative to the
6032
6134
  given document top, which defaults to the screen position of the
6033
6135
  document (forcing a layout).
6136
+
6137
+ *Deprecated: use `viewportLineBlocks` instead.*
6034
6138
  */
6035
6139
  viewportLines(f, docTop) {
6036
- let { from, to } = this.viewport;
6037
- this.viewState.forEachLine(from, to, f, ensureTop(docTop, this.contentDOM));
6140
+ let top = ensureTop(docTop, this);
6141
+ for (let line of this.viewportLineBlocks)
6142
+ f(line.moveY(top));
6143
+ }
6144
+ /**
6145
+ Get the extent and vertical position of all [line
6146
+ blocks](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt) in the viewport. Positions
6147
+ are relative to the [top of the
6148
+ document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop);
6149
+ */
6150
+ get viewportLineBlocks() {
6151
+ return this.viewState.viewportLines;
6038
6152
  }
6039
6153
  /**
6040
6154
  Find the extent and height of the visual line (a range delimited
@@ -6045,9 +6159,22 @@ class EditorView {
6045
6159
  argument, which defaults to 0 for this method. You can pass
6046
6160
  `view.contentDOM.getBoundingClientRect().top` here to get screen
6047
6161
  coordinates.
6162
+
6163
+ *Deprecated: use `lineBlockAt` instead.*
6048
6164
  */
6049
6165
  visualLineAt(pos, docTop = 0) {
6050
- return this.viewState.lineAt(pos, docTop);
6166
+ return this.lineBlockAt(pos).moveY(docTop + this.viewState.paddingTop);
6167
+ }
6168
+ /**
6169
+ Find the line block around the given document position. A line
6170
+ block is a range delimited on both sides by either a
6171
+ non-[hidden](https://codemirror.net/6/docs/ref/#view.Decoration^range) line breaks, or the
6172
+ start/end of the document. It will usually just hold a line of
6173
+ text, but may be broken into multiple textblocks by block
6174
+ widgets.
6175
+ */
6176
+ lineBlockAt(pos) {
6177
+ return this.viewState.lineBlockAt(pos);
6051
6178
  }
6052
6179
  /**
6053
6180
  The editor's total content height.
@@ -6380,8 +6507,9 @@ search match).
6380
6507
  EditorView.announce = /*@__PURE__*/StateEffect.define();
6381
6508
  // Maximum line length for which we compute accurate bidi info
6382
6509
  const MaxBidiLine = 4096;
6383
- function ensureTop(given, dom) {
6384
- return given == null ? dom.getBoundingClientRect().top : given;
6510
+ // FIXME remove this and its callers on next breaking release
6511
+ function ensureTop(given, view) {
6512
+ return (given == null ? view.contentDOM.getBoundingClientRect().top : given) + view.viewState.paddingTop;
6385
6513
  }
6386
6514
  let resizeDebounce = -1;
6387
6515
  function ensureGlobalHandler() {
@@ -6732,7 +6860,7 @@ function wrappedLine(view, pos, inside) {
6732
6860
  type: BlockType.Text };
6733
6861
  }
6734
6862
  function blockAt(view, pos) {
6735
- let line = view.visualLineAt(pos);
6863
+ let line = view.lineBlockAt(pos);
6736
6864
  if (Array.isArray(line.type))
6737
6865
  for (let l of line.type) {
6738
6866
  if (l.to > pos || l.to == pos && (l.to == line.to || l.type == BlockType.Text))
@@ -7123,7 +7251,7 @@ const activeLineHighlighter = /*@__PURE__*/ViewPlugin.fromClass(class {
7123
7251
  for (let r of view.state.selection.ranges) {
7124
7252
  if (!r.empty)
7125
7253
  return Decoration.none;
7126
- let line = view.visualLineAt(r.head);
7254
+ let line = view.lineBlockAt(r.head);
7127
7255
  if (line.from > lastLineStart) {
7128
7256
  deco.push(lineDeco.range(line.from));
7129
7257
  lastLineStart = line.from;