@codemirror/view 0.19.20 → 0.19.24

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.cjs CHANGED
@@ -186,7 +186,7 @@ function scrollRectIntoView(dom, rect, side, center) {
186
186
  }
187
187
  }
188
188
  }
189
- class DOMSelection {
189
+ class DOMSelectionState {
190
190
  constructor() {
191
191
  this.anchorNode = null;
192
192
  this.anchorOffset = 0;
@@ -197,11 +197,14 @@ class DOMSelection {
197
197
  return this.anchorNode == domSel.anchorNode && this.anchorOffset == domSel.anchorOffset &&
198
198
  this.focusNode == domSel.focusNode && this.focusOffset == domSel.focusOffset;
199
199
  }
200
- set(domSel) {
201
- this.anchorNode = domSel.anchorNode;
202
- this.anchorOffset = domSel.anchorOffset;
203
- this.focusNode = domSel.focusNode;
204
- this.focusOffset = domSel.focusOffset;
200
+ setRange(range) {
201
+ this.set(range.anchorNode, range.anchorOffset, range.focusNode, range.focusOffset);
202
+ }
203
+ set(anchorNode, anchorOffset, focusNode, focusOffset) {
204
+ this.anchorNode = anchorNode;
205
+ this.anchorOffset = anchorOffset;
206
+ this.focusNode = focusNode;
207
+ this.focusOffset = focusOffset;
205
208
  }
206
209
  }
207
210
  let preventScrollSupported = null;
@@ -1909,7 +1912,7 @@ class ViewUpdate {
1909
1912
  Whether the document changed in this update.
1910
1913
  */
1911
1914
  get docChanged() {
1912
- return this.transactions.some(tr => tr.docChanged);
1915
+ return !this.changes.empty;
1913
1916
  }
1914
1917
  /**
1915
1918
  Whether the selection was explicitly set in this update.
@@ -1943,9 +1946,10 @@ class DocView extends ContentView {
1943
1946
  // we don't mess it up when reading it back it
1944
1947
  this.impreciseAnchor = null;
1945
1948
  this.impreciseHead = null;
1949
+ this.forceSelection = false;
1946
1950
  // Used by the resize observer to ignore resizes that we caused
1947
1951
  // ourselves
1948
- this.lastUpdate = 0;
1952
+ this.lastUpdate = Date.now();
1949
1953
  this.setDOM(view.contentDOM);
1950
1954
  this.children = [new LineView];
1951
1955
  this.children[0].setParent(this);
@@ -1959,7 +1963,6 @@ class DocView extends ContentView {
1959
1963
  // position, if we know the editor is going to scroll that position
1960
1964
  // into view.
1961
1965
  update(update) {
1962
- this.lastUpdate = Date.now();
1963
1966
  let changedRanges = update.changedRanges;
1964
1967
  if (this.minWidth > 0 && changedRanges.length) {
1965
1968
  if (!changedRanges.every(({ fromA, toA }) => toA < this.minWidthFrom || fromA > this.minWidthTo)) {
@@ -1979,21 +1982,19 @@ class DocView extends ContentView {
1979
1982
  // getSelection than the one that it actually shows to the user.
1980
1983
  // This forces a selection update when lines are joined to work
1981
1984
  // around that. Issue #54
1982
- let forceSelection = (browser.ie || browser.chrome) && !this.compositionDeco.size && update &&
1983
- update.state.doc.lines != update.startState.doc.lines;
1985
+ if ((browser.ie || browser.chrome) && !this.compositionDeco.size && update &&
1986
+ update.state.doc.lines != update.startState.doc.lines)
1987
+ this.forceSelection = true;
1984
1988
  let prevDeco = this.decorations, deco = this.updateDeco();
1985
1989
  let decoDiff = findChangedDeco(prevDeco, deco, update.changes);
1986
1990
  changedRanges = ChangedRange.extendWithRanges(changedRanges, decoDiff);
1987
- let pointerSel = update.transactions.some(tr => tr.isUserEvent("select.pointer"));
1988
- if (this.dirty == 0 /* Not */ && changedRanges.length == 0 &&
1989
- !(update.flags & 4 /* Viewport */) &&
1990
- update.state.selection.main.from >= this.view.viewport.from &&
1991
- update.state.selection.main.to <= this.view.viewport.to) {
1992
- this.updateSelection(forceSelection, pointerSel);
1991
+ if (this.dirty == 0 /* Not */ && changedRanges.length == 0) {
1993
1992
  return false;
1994
1993
  }
1995
1994
  else {
1996
- this.updateInner(changedRanges, deco, update.startState.doc.length, forceSelection, pointerSel);
1995
+ this.updateInner(changedRanges, deco, update.startState.doc.length);
1996
+ if (update.transactions.length)
1997
+ this.lastUpdate = Date.now();
1997
1998
  return true;
1998
1999
  }
1999
2000
  }
@@ -2001,13 +2002,16 @@ class DocView extends ContentView {
2001
2002
  if (this.dirty) {
2002
2003
  this.view.observer.ignore(() => this.view.docView.sync());
2003
2004
  this.dirty = 0 /* Not */;
2005
+ this.updateSelection(true);
2004
2006
  }
2005
- if (sel)
2007
+ else {
2006
2008
  this.updateSelection();
2009
+ }
2007
2010
  }
2008
2011
  // Used both by update and checkLayout do perform the actual DOM
2009
2012
  // update
2010
- updateInner(changes, deco, oldLength, forceSelection = false, pointerSel = false) {
2013
+ updateInner(changes, deco, oldLength) {
2014
+ this.view.viewState.mustMeasureContent = true;
2011
2015
  this.updateChildren(changes, deco, oldLength);
2012
2016
  let { observer } = this.view;
2013
2017
  observer.ignore(() => {
@@ -2015,7 +2019,7 @@ class DocView extends ContentView {
2015
2019
  // messes with the scroll position during DOM mutation (though
2016
2020
  // no relayout is triggered and I cannot imagine how it can
2017
2021
  // recompute the scroll position without a layout)
2018
- this.dom.style.height = this.view.viewState.domHeight + "px";
2022
+ this.dom.style.height = this.view.viewState.contentHeight + "px";
2019
2023
  this.dom.style.minWidth = this.minWidth ? this.minWidth + "px" : "";
2020
2024
  // Chrome will sometimes, when DOM mutations occur directly
2021
2025
  // around the selection, get confused and report a different
@@ -2025,8 +2029,7 @@ class DocView extends ContentView {
2025
2029
  this.sync(track);
2026
2030
  this.dirty = 0 /* Not */;
2027
2031
  if (track && (track.written || observer.selectionRange.focusNode != track.node))
2028
- forceSelection = true;
2029
- this.updateSelection(forceSelection, pointerSel);
2032
+ this.forceSelection = true;
2030
2033
  this.dom.style.height = "";
2031
2034
  });
2032
2035
  let gaps = [];
@@ -2112,10 +2115,14 @@ class DocView extends ContentView {
2112
2115
  this.replaceChildren(fromI, toI, content);
2113
2116
  }
2114
2117
  // Sync the DOM selection to this.state.selection
2115
- updateSelection(force = false, fromPointer = false) {
2118
+ updateSelection(mustRead = false, fromPointer = false) {
2119
+ if (mustRead)
2120
+ this.view.observer.readSelectionRange();
2116
2121
  if (!(fromPointer || this.mayControlSelection()) ||
2117
2122
  browser.ios && this.view.inputState.rapidCompositionStart)
2118
2123
  return;
2124
+ let force = this.forceSelection;
2125
+ this.forceSelection = false;
2119
2126
  let main = this.view.state.selection.main;
2120
2127
  // FIXME need to handle the case where the selection falls inside a block range
2121
2128
  let anchor = this.domAtPos(main.anchor);
@@ -2297,7 +2304,7 @@ class DocView extends ContentView {
2297
2304
  let next = i == vs.viewports.length ? null : vs.viewports[i];
2298
2305
  let end = next ? next.from - 1 : this.length;
2299
2306
  if (end > pos) {
2300
- let height = vs.lineAt(end, 0).bottom - vs.lineAt(pos, 0).top;
2307
+ let height = vs.lineBlockAt(end).bottom - vs.lineBlockAt(pos).top;
2301
2308
  deco.push(Decoration.replace({ widget: new BlockGapWidget(height), block: true, inclusive: true }).range(pos, end));
2302
2309
  }
2303
2310
  if (!next)
@@ -2904,13 +2911,14 @@ function domPosInText(node, x, y) {
2904
2911
  }
2905
2912
  function posAtCoords(view, { x, y }, precise, bias = -1) {
2906
2913
  var _a;
2907
- let content = view.contentDOM.getBoundingClientRect(), block;
2914
+ let content = view.contentDOM.getBoundingClientRect(), docTop = content.top + view.viewState.paddingTop;
2908
2915
  let halfLine = view.defaultLineHeight / 2;
2916
+ let block, yOffset = y - docTop;
2909
2917
  for (let bounced = false;;) {
2910
- block = view.blockAtHeight(y, content.top);
2911
- if (block.top > y || block.bottom < y) {
2912
- bias = block.top > y ? -1 : 1;
2913
- y = Math.min(block.bottom - halfLine, Math.max(block.top + halfLine, y));
2918
+ block = view.elementAtHeight(yOffset);
2919
+ if (block.top > yOffset || block.bottom < yOffset) {
2920
+ bias = block.top > yOffset ? -1 : 1;
2921
+ yOffset = Math.min(block.bottom - halfLine, Math.max(block.top + halfLine, yOffset));
2914
2922
  if (bounced)
2915
2923
  return precise ? null : 0;
2916
2924
  else
@@ -2918,8 +2926,9 @@ function posAtCoords(view, { x, y }, precise, bias = -1) {
2918
2926
  }
2919
2927
  if (block.type == exports.BlockType.Text)
2920
2928
  break;
2921
- y = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2929
+ yOffset = bias > 0 ? block.bottom + halfLine : block.top - halfLine;
2922
2930
  }
2931
+ y = docTop + yOffset;
2923
2932
  let lineStart = block.from;
2924
2933
  // Clip x to the viewport sides
2925
2934
  x = Math.max(content.left + 1, Math.min(content.right - 1, x));
@@ -3032,17 +3041,17 @@ function moveVertically(view, start, forward, distance) {
3032
3041
  return state.EditorSelection.cursor(startPos);
3033
3042
  let goal = start.goalColumn, startY;
3034
3043
  let rect = view.contentDOM.getBoundingClientRect();
3035
- let startCoords = view.coordsAtPos(startPos);
3044
+ let startCoords = view.coordsAtPos(startPos), docTop = view.documentTop;
3036
3045
  if (startCoords) {
3037
3046
  if (goal == null)
3038
3047
  goal = startCoords.left - rect.left;
3039
3048
  startY = dir < 0 ? startCoords.top : startCoords.bottom;
3040
3049
  }
3041
3050
  else {
3042
- let line = view.viewState.lineAt(startPos, view.dom.getBoundingClientRect().top);
3051
+ let line = view.viewState.lineBlockAt(startPos - docTop);
3043
3052
  if (goal == null)
3044
3053
  goal = Math.min(rect.right - rect.left, view.defaultCharacterWidth * (startPos - line.from));
3045
- startY = dir < 0 ? line.top : line.bottom;
3054
+ startY = (dir < 0 ? line.top : line.bottom) + docTop;
3046
3055
  }
3047
3056
  let resolvedGoal = rect.left + goal;
3048
3057
  let dist = distance !== null && distance !== void 0 ? distance : (view.defaultLineHeight >> 1);
@@ -3293,7 +3302,7 @@ class MouseSelection {
3293
3302
  this.extend = startEvent.shiftKey;
3294
3303
  this.multiple = view.state.facet(state.EditorState.allowMultipleSelections) && addsSelectionRange(view, startEvent);
3295
3304
  this.dragMove = dragMovesSelection(view, startEvent);
3296
- this.dragging = isInPrimarySelection(view, startEvent) ? null : false;
3305
+ this.dragging = isInPrimarySelection(view, startEvent) && getClickType(startEvent) == 1 ? null : false;
3297
3306
  // When clicking outside of the selection, immediately apply the
3298
3307
  // effect of starting the selection
3299
3308
  if (this.dragging === false) {
@@ -3514,7 +3523,7 @@ function basicMouseSelection(view, event) {
3514
3523
  let last = start, lastEvent = event;
3515
3524
  return {
3516
3525
  update(update) {
3517
- if (update.changes) {
3526
+ if (update.docChanged) {
3518
3527
  if (start)
3519
3528
  start.pos = update.changes.mapPos(start.pos);
3520
3529
  startSel = startSel.map(update.changes);
@@ -3778,7 +3787,10 @@ class HeightOracle {
3778
3787
  return lines * this.lineHeight;
3779
3788
  }
3780
3789
  setDoc(doc) { this.doc = doc; return this; }
3781
- mustRefresh(lineHeights, whiteSpace, direction) {
3790
+ mustRefreshForStyle(whiteSpace, direction) {
3791
+ return (wrappingWhiteSpace.indexOf(whiteSpace) > -1) != this.lineWrapping || this.direction != direction;
3792
+ }
3793
+ mustRefreshForHeights(lineHeights) {
3782
3794
  let newHeight = false;
3783
3795
  for (let i = 0; i < lineHeights.length; i++) {
3784
3796
  let h = lineHeights[i];
@@ -3790,7 +3802,7 @@ class HeightOracle {
3790
3802
  this.heightSamples[Math.floor(h * 10)] = true;
3791
3803
  }
3792
3804
  }
3793
- return newHeight || (wrappingWhiteSpace.indexOf(whiteSpace) > -1) != this.lineWrapping || this.direction != direction;
3805
+ return newHeight;
3794
3806
  }
3795
3807
  refresh(whiteSpace, direction, lineHeight, charWidth, lineLength, knownHeights) {
3796
3808
  let lineWrapping = wrappingWhiteSpace.indexOf(whiteSpace) > -1;
@@ -3844,7 +3856,8 @@ class BlockInfo {
3844
3856
  */
3845
3857
  length,
3846
3858
  /**
3847
- The top position of the element.
3859
+ The top position of the element (relative to the top of the
3860
+ document).
3848
3861
  */
3849
3862
  top,
3850
3863
  /**
@@ -3878,6 +3891,12 @@ class BlockInfo {
3878
3891
  .concat(Array.isArray(other.type) ? other.type : [other]);
3879
3892
  return new BlockInfo(this.from, this.length + other.length, this.top, this.height + other.height, detail);
3880
3893
  }
3894
+ /**
3895
+ FIXME remove on next breaking release @internal
3896
+ */
3897
+ moveY(offset) {
3898
+ 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);
3899
+ }
3881
3900
  }
3882
3901
  var QueryType;
3883
3902
  (function (QueryType) {
@@ -3885,7 +3904,7 @@ var QueryType;
3885
3904
  QueryType[QueryType["ByHeight"] = 1] = "ByHeight";
3886
3905
  QueryType[QueryType["ByPosNoHeight"] = 2] = "ByPosNoHeight";
3887
3906
  })(QueryType || (QueryType = {}));
3888
- const Epsilon = 1e-4;
3907
+ const Epsilon = 1e-3;
3889
3908
  class HeightMap {
3890
3909
  constructor(length, // The number of characters covered
3891
3910
  height, // Height of this part of the document
@@ -4112,22 +4131,30 @@ class HeightMapGap extends HeightMap {
4112
4131
  // can't be widgets or collapsed ranges in those lines, because
4113
4132
  // they would already have been added to the heightmap (gaps
4114
4133
  // only contain plain text).
4115
- let nodes = [], pos = Math.max(offset, measured.from);
4134
+ let nodes = [], pos = Math.max(offset, measured.from), singleHeight = -1;
4135
+ let wasChanged = oracle.heightChanged;
4116
4136
  if (measured.from > offset)
4117
4137
  nodes.push(new HeightMapGap(measured.from - offset - 1).updateHeight(oracle, offset));
4118
4138
  while (pos <= end && measured.more) {
4119
4139
  let len = oracle.doc.lineAt(pos).length;
4120
4140
  if (nodes.length)
4121
4141
  nodes.push(null);
4122
- let line = new HeightMapText(len, measured.heights[measured.index++]);
4142
+ let height = measured.heights[measured.index++];
4143
+ if (singleHeight == -1)
4144
+ singleHeight = height;
4145
+ else if (Math.abs(height - singleHeight) >= Epsilon)
4146
+ singleHeight = -2;
4147
+ let line = new HeightMapText(len, height);
4123
4148
  line.outdated = false;
4124
4149
  nodes.push(line);
4125
4150
  pos += len + 1;
4126
4151
  }
4127
4152
  if (pos <= end)
4128
4153
  nodes.push(null, new HeightMapGap(end - pos).updateHeight(oracle, pos));
4129
- oracle.heightChanged = true;
4130
- return HeightMap.of(nodes);
4154
+ let result = HeightMap.of(nodes);
4155
+ oracle.heightChanged = wasChanged || singleHeight < 0 || Math.abs(result.height - this.height) >= Epsilon ||
4156
+ Math.abs(singleHeight - this.lines(oracle.doc, offset).lineHeight) >= Epsilon;
4157
+ return result;
4131
4158
  }
4132
4159
  else if (force || this.outdated) {
4133
4160
  this.setHeight(oracle, oracle.heightForGap(offset, offset + this.length));
@@ -4485,7 +4512,8 @@ class ViewState {
4485
4512
  this.inView = true;
4486
4513
  this.paddingTop = 0;
4487
4514
  this.paddingBottom = 0;
4488
- this.contentWidth = 0;
4515
+ this.contentDOMWidth = 0;
4516
+ this.contentDOMHeight = 0;
4489
4517
  this.editorHeight = 0;
4490
4518
  this.heightOracle = new HeightOracle;
4491
4519
  // See VP.MaxDOMHeight
@@ -4493,6 +4521,9 @@ class ViewState {
4493
4521
  this.scrollTarget = null;
4494
4522
  // Briefly set to true when printing, to disable viewport limiting
4495
4523
  this.printing = false;
4524
+ // Flag set when editor content was redrawn, so that the next
4525
+ // measure stage knows it must read DOM layout
4526
+ this.mustMeasureContent = true;
4496
4527
  this.visibleRanges = [];
4497
4528
  // Cursor 'assoc' is only significant when the cursor is on a line
4498
4529
  // wrap point, where it must stick to the character that it is
@@ -4505,6 +4536,7 @@ class ViewState {
4505
4536
  this.mustEnforceCursorAssoc = false;
4506
4537
  this.heightMap = HeightMap.empty().applyChanges(state.facet(decorations), text.Text.empty, this.heightOracle.setDoc(state.doc), [new ChangedRange(0, 0, 0, state.doc.length)]);
4507
4538
  this.viewport = this.getViewport(0, null);
4539
+ this.updateViewportLines();
4508
4540
  this.updateForViewport();
4509
4541
  this.lineGaps = this.ensureLineGaps([]);
4510
4542
  this.lineGapDeco = Decoration.set(this.lineGaps.map(gap => gap.draw(false)));
@@ -4515,7 +4547,7 @@ class ViewState {
4515
4547
  for (let i = 0; i <= 1; i++) {
4516
4548
  let pos = i ? main.head : main.anchor;
4517
4549
  if (!viewports.some(({ from, to }) => pos >= from && pos <= to)) {
4518
- let { from, to } = this.lineAt(pos, 0);
4550
+ let { from, to } = this.lineBlockAt(pos);
4519
4551
  viewports.push(new Viewport(from, to));
4520
4552
  }
4521
4553
  }
@@ -4523,6 +4555,12 @@ class ViewState {
4523
4555
  this.scaler = this.heightMap.height <= 7000000 /* MaxDOMHeight */ ? IdScaler :
4524
4556
  new BigScaler(this.heightOracle.doc, this.heightMap, this.viewports);
4525
4557
  }
4558
+ updateViewportLines() {
4559
+ this.viewportLines = [];
4560
+ this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.state.doc, 0, 0, block => {
4561
+ this.viewportLines.push(this.scaler.scale == 1 ? block : scaleBlock(block, this.scaler));
4562
+ });
4563
+ }
4526
4564
  update(update, scrollTarget = null) {
4527
4565
  let prev = this.state;
4528
4566
  this.state = update.state;
@@ -4537,7 +4575,11 @@ class ViewState {
4537
4575
  if (scrollTarget && (scrollTarget.range.head < viewport.from || scrollTarget.range.head > viewport.to) ||
4538
4576
  !this.viewportIsAppropriate(viewport))
4539
4577
  viewport = this.getViewport(0, scrollTarget);
4578
+ let updateLines = !update.changes.empty || (update.flags & 2 /* Height */) ||
4579
+ viewport.from != this.viewport.from || viewport.to != this.viewport.to;
4540
4580
  this.viewport = viewport;
4581
+ if (updateLines)
4582
+ this.updateViewportLines();
4541
4583
  this.updateForViewport();
4542
4584
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4543
4585
  this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps, update.changes)));
@@ -4548,13 +4590,17 @@ class ViewState {
4548
4590
  update.state.selection.main.empty && update.state.selection.main.assoc)
4549
4591
  this.mustEnforceCursorAssoc = true;
4550
4592
  }
4551
- measure(docView, repeated) {
4552
- let dom = docView.dom, whiteSpace = "", direction = exports.Direction.LTR;
4553
- let result = 0;
4554
- if (!repeated) {
4593
+ measure(view) {
4594
+ let dom = view.contentDOM, style = window.getComputedStyle(dom);
4595
+ let oracle = this.heightOracle;
4596
+ let whiteSpace = style.whiteSpace, direction = style.direction == "rtl" ? exports.Direction.RTL : exports.Direction.LTR;
4597
+ let refresh = this.heightOracle.mustRefreshForStyle(whiteSpace, direction);
4598
+ let measureContent = refresh || this.mustMeasureContent || this.contentDOMHeight != dom.clientHeight;
4599
+ let result = 0, bias = 0;
4600
+ if (measureContent) {
4601
+ this.mustMeasureContent = false;
4602
+ this.contentDOMHeight = dom.clientHeight;
4555
4603
  // Vertical padding
4556
- let style = window.getComputedStyle(dom);
4557
- whiteSpace = style.whiteSpace, direction = (style.direction == "rtl" ? exports.Direction.RTL : exports.Direction.LTR);
4558
4604
  let paddingTop = parseInt(style.paddingTop) || 0, paddingBottom = parseInt(style.paddingBottom) || 0;
4559
4605
  if (this.paddingTop != paddingTop || this.paddingBottom != paddingBottom) {
4560
4606
  result |= 8 /* Geometry */;
@@ -4569,39 +4615,42 @@ class ViewState {
4569
4615
  this.inView = this.pixelViewport.bottom > this.pixelViewport.top && this.pixelViewport.right > this.pixelViewport.left;
4570
4616
  if (!this.inView)
4571
4617
  return 0;
4572
- let lineHeights = docView.measureVisibleLineHeights();
4573
- let refresh = false, bias = 0, oracle = this.heightOracle;
4574
- if (!repeated) {
4575
- let contentWidth = docView.dom.clientWidth;
4576
- if (oracle.mustRefresh(lineHeights, whiteSpace, direction) ||
4577
- oracle.lineWrapping && Math.abs(contentWidth - this.contentWidth) > oracle.charWidth) {
4578
- let { lineHeight, charWidth } = docView.measureTextSize();
4618
+ if (measureContent) {
4619
+ let lineHeights = view.docView.measureVisibleLineHeights();
4620
+ if (oracle.mustRefreshForHeights(lineHeights))
4621
+ refresh = true;
4622
+ let contentWidth = dom.clientWidth;
4623
+ if (refresh || oracle.lineWrapping && Math.abs(contentWidth - this.contentDOMWidth) > oracle.charWidth) {
4624
+ let { lineHeight, charWidth } = view.docView.measureTextSize();
4579
4625
  refresh = oracle.refresh(whiteSpace, direction, lineHeight, charWidth, contentWidth / charWidth, lineHeights);
4580
4626
  if (refresh) {
4581
- docView.minWidth = 0;
4627
+ view.docView.minWidth = 0;
4582
4628
  result |= 8 /* Geometry */;
4583
4629
  }
4584
4630
  }
4585
- if (this.contentWidth != contentWidth) {
4586
- this.contentWidth = contentWidth;
4631
+ if (this.contentDOMWidth != contentWidth) {
4632
+ this.contentDOMWidth = contentWidth;
4587
4633
  result |= 8 /* Geometry */;
4588
4634
  }
4589
- if (this.editorHeight != docView.view.scrollDOM.clientHeight) {
4590
- this.editorHeight = docView.view.scrollDOM.clientHeight;
4635
+ if (this.editorHeight != view.scrollDOM.clientHeight) {
4636
+ this.editorHeight = view.scrollDOM.clientHeight;
4591
4637
  result |= 8 /* Geometry */;
4592
4638
  }
4593
4639
  if (dTop > 0 && dBottom > 0)
4594
4640
  bias = Math.max(dTop, dBottom);
4595
4641
  else if (dTop < 0 && dBottom < 0)
4596
4642
  bias = Math.min(dTop, dBottom);
4597
- }
4598
- oracle.heightChanged = false;
4599
- this.heightMap = this.heightMap.updateHeight(oracle, 0, refresh, new MeasuredHeights(this.viewport.from, lineHeights));
4600
- if (oracle.heightChanged)
4601
- result |= 2 /* Height */;
4602
- if (!this.viewportIsAppropriate(this.viewport, bias) ||
4603
- this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to))
4643
+ oracle.heightChanged = false;
4644
+ this.heightMap = this.heightMap.updateHeight(oracle, 0, refresh, new MeasuredHeights(this.viewport.from, lineHeights));
4645
+ if (oracle.heightChanged)
4646
+ result |= 2 /* Height */;
4647
+ }
4648
+ let viewportChange = !this.viewportIsAppropriate(this.viewport, bias) ||
4649
+ this.scrollTarget && (this.scrollTarget.range.head < this.viewport.from || this.scrollTarget.range.head > this.viewport.to);
4650
+ if (viewportChange)
4604
4651
  this.viewport = this.getViewport(bias, this.scrollTarget);
4652
+ if ((result & 2 /* Height */) || viewportChange)
4653
+ this.updateViewportLines();
4605
4654
  this.updateForViewport();
4606
4655
  if (this.lineGaps.length || this.viewport.to - this.viewport.from > 4000 /* DoubleMargin */)
4607
4656
  this.updateLineGaps(this.ensureLineGaps(refresh ? [] : this.lineGaps));
@@ -4612,12 +4661,12 @@ class ViewState {
4612
4661
  // to a line end is going to trigger a layout anyway, so it
4613
4662
  // can't be a pure write. It should be rare that it does any
4614
4663
  // writing.
4615
- docView.enforceCursorAssoc();
4664
+ view.docView.enforceCursorAssoc();
4616
4665
  }
4617
4666
  return result;
4618
4667
  }
4619
- get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top, 0); }
4620
- get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom, 0); }
4668
+ get visibleTop() { return this.scaler.fromDOM(this.pixelViewport.top); }
4669
+ get visibleBottom() { return this.scaler.fromDOM(this.pixelViewport.bottom); }
4621
4670
  getViewport(bias, scrollTarget) {
4622
4671
  // This will divide VP.Margin between the top and the
4623
4672
  // bottom, depending on the bias (the change in viewport position
@@ -4677,12 +4726,12 @@ class ViewState {
4677
4726
  // This won't work at all in predominantly right-to-left text.
4678
4727
  if (this.heightOracle.direction != exports.Direction.LTR)
4679
4728
  return gaps;
4680
- this.heightMap.forEachLine(this.viewport.from, this.viewport.to, this.state.doc, 0, 0, line => {
4729
+ for (let line of this.viewportLines) {
4681
4730
  if (line.length < 4000 /* DoubleMargin */)
4682
- return;
4731
+ continue;
4683
4732
  let structure = lineStructure(line.from, line.to, this.state);
4684
4733
  if (structure.total < 4000 /* DoubleMargin */)
4685
- return;
4734
+ continue;
4686
4735
  let viewFrom, viewTo;
4687
4736
  if (this.heightOracle.lineWrapping) {
4688
4737
  let marginHeight = (2000 /* Margin */ / this.heightOracle.lineLength) * this.heightOracle.lineHeight;
@@ -4712,7 +4761,7 @@ class ViewState {
4712
4761
  Math.abs(gap.from - from) < 1000 /* HalfMargin */ && Math.abs(gap.to - to) < 1000 /* HalfMargin */) ||
4713
4762
  new LineGap(from, to, this.gapSize(line, from, to, structure)));
4714
4763
  }
4715
- });
4764
+ }
4716
4765
  return gaps;
4717
4766
  }
4718
4767
  gapSize(line, from, to, structure) {
@@ -4744,27 +4793,18 @@ class ViewState {
4744
4793
  this.visibleRanges = ranges;
4745
4794
  return changed ? 4 /* Viewport */ : 0;
4746
4795
  }
4747
- lineAt(pos, editorTop) {
4748
- editorTop += this.paddingTop;
4749
- return scaleBlock(this.heightMap.lineAt(pos, QueryType.ByPos, this.state.doc, editorTop, 0), this.scaler, editorTop);
4796
+ lineBlockAt(pos) {
4797
+ return (pos >= this.viewport.from && pos <= this.viewport.to && this.viewportLines.find(b => b.from <= pos && b.to >= pos)) ||
4798
+ scaleBlock(this.heightMap.lineAt(pos, QueryType.ByPos, this.state.doc, 0, 0), this.scaler);
4750
4799
  }
4751
- lineAtHeight(height, editorTop) {
4752
- editorTop += this.paddingTop;
4753
- return scaleBlock(this.heightMap.lineAt(this.scaler.fromDOM(height, editorTop), QueryType.ByHeight, this.state.doc, editorTop, 0), this.scaler, editorTop);
4800
+ lineBlockAtHeight(height) {
4801
+ return scaleBlock(this.heightMap.lineAt(this.scaler.fromDOM(height), QueryType.ByHeight, this.state.doc, 0, 0), this.scaler);
4754
4802
  }
4755
- blockAtHeight(height, editorTop) {
4756
- editorTop += this.paddingTop;
4757
- return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height, editorTop), this.state.doc, editorTop, 0), this.scaler, editorTop);
4758
- }
4759
- forEachLine(from, to, f, editorTop) {
4760
- editorTop += this.paddingTop;
4761
- return this.heightMap.forEachLine(from, to, this.state.doc, editorTop, 0, this.scaler.scale == 1 ? f : b => f(scaleBlock(b, this.scaler, editorTop)));
4803
+ elementAtHeight(height) {
4804
+ return scaleBlock(this.heightMap.blockAt(this.scaler.fromDOM(height), this.state.doc, 0, 0), this.scaler);
4762
4805
  }
4763
4806
  get contentHeight() {
4764
- return this.domHeight + this.paddingTop + this.paddingBottom;
4765
- }
4766
- get domHeight() {
4767
- return this.scaler.toDOM(this.heightMap.height, this.paddingTop);
4807
+ return this.scaler.toDOM(this.heightMap.height) + this.paddingTop + this.paddingBottom;
4768
4808
  }
4769
4809
  }
4770
4810
  class Viewport {
@@ -4861,36 +4901,34 @@ class BigScaler {
4861
4901
  base = obj.bottom;
4862
4902
  }
4863
4903
  }
4864
- toDOM(n, top) {
4865
- n -= top;
4904
+ toDOM(n) {
4866
4905
  for (let i = 0, base = 0, domBase = 0;; i++) {
4867
4906
  let vp = i < this.viewports.length ? this.viewports[i] : null;
4868
4907
  if (!vp || n < vp.top)
4869
- return domBase + (n - base) * this.scale + top;
4908
+ return domBase + (n - base) * this.scale;
4870
4909
  if (n <= vp.bottom)
4871
- return vp.domTop + (n - vp.top) + top;
4910
+ return vp.domTop + (n - vp.top);
4872
4911
  base = vp.bottom;
4873
4912
  domBase = vp.domBottom;
4874
4913
  }
4875
4914
  }
4876
- fromDOM(n, top) {
4877
- n -= top;
4915
+ fromDOM(n) {
4878
4916
  for (let i = 0, base = 0, domBase = 0;; i++) {
4879
4917
  let vp = i < this.viewports.length ? this.viewports[i] : null;
4880
4918
  if (!vp || n < vp.domTop)
4881
- return base + (n - domBase) / this.scale + top;
4919
+ return base + (n - domBase) / this.scale;
4882
4920
  if (n <= vp.domBottom)
4883
- return vp.top + (n - vp.domTop) + top;
4921
+ return vp.top + (n - vp.domTop);
4884
4922
  base = vp.bottom;
4885
4923
  domBase = vp.domBottom;
4886
4924
  }
4887
4925
  }
4888
4926
  }
4889
- function scaleBlock(block, scaler, top) {
4927
+ function scaleBlock(block, scaler) {
4890
4928
  if (scaler.scale == 1)
4891
4929
  return block;
4892
- let bTop = scaler.toDOM(block.top, top), bBottom = scaler.toDOM(block.bottom, top);
4893
- return new BlockInfo(block.from, block.length, bTop, bBottom - bTop, Array.isArray(block.type) ? block.type.map(b => scaleBlock(b, scaler, top)) : block.type);
4930
+ let bTop = scaler.toDOM(block.top), bBottom = scaler.toDOM(block.bottom);
4931
+ return new BlockInfo(block.from, block.length, bTop, bBottom - bTop, Array.isArray(block.type) ? block.type.map(b => scaleBlock(b, scaler)) : block.type);
4894
4932
  }
4895
4933
 
4896
4934
  const theme = state.Facet.define({ combine: strs => strs.join(" ") });
@@ -5075,25 +5113,30 @@ class DOMObserver {
5075
5113
  this.onChange = onChange;
5076
5114
  this.onScrollChanged = onScrollChanged;
5077
5115
  this.active = false;
5078
- this.ignoreSelection = new DOMSelection;
5116
+ // The known selection. Kept in our own object, as opposed to just
5117
+ // directly accessing the selection because:
5118
+ // - Safari doesn't report the right selection in shadow DOM
5119
+ // - Reading from the selection forces a DOM layout
5120
+ // - This way, we can ignore selectionchange events if we have
5121
+ // already seen the 'new' selection
5122
+ this.selectionRange = new DOMSelectionState;
5123
+ // Set when a selection change is detected, cleared on flush
5124
+ this.selectionChanged = false;
5079
5125
  this.delayedFlush = -1;
5126
+ this.resizeTimeout = -1;
5080
5127
  this.queue = [];
5081
- this.lastFlush = 0;
5082
5128
  this.scrollTargets = [];
5083
5129
  this.intersection = null;
5084
5130
  this.resize = null;
5085
5131
  this.intersecting = false;
5086
5132
  this.gapIntersection = null;
5087
5133
  this.gaps = [];
5088
- // Used to work around a Safari Selection/shadow DOM bug (#414)
5089
- this._selectionRange = null;
5090
5134
  // Timeout for scheduling check of the parents that need scroll handlers
5091
5135
  this.parentCheck = -1;
5092
5136
  this.dom = view.contentDOM;
5093
5137
  this.observer = new MutationObserver(mutations => {
5094
5138
  for (let mut of mutations)
5095
5139
  this.queue.push(mut);
5096
- this._selectionRange = null;
5097
5140
  // IE11 will sometimes (on typing over a selection or
5098
5141
  // backspacing out a single character text node) call the
5099
5142
  // observer callback before actually updating the DOM.
@@ -5120,8 +5163,11 @@ class DOMObserver {
5120
5163
  this.onSelectionChange = this.onSelectionChange.bind(this);
5121
5164
  if (typeof ResizeObserver == "function") {
5122
5165
  this.resize = new ResizeObserver(() => {
5123
- if (this.view.docView.lastUpdate < Date.now() - 100)
5124
- this.view.requestMeasure();
5166
+ if (this.view.docView.lastUpdate < Date.now() - 75 && this.resizeTimeout < 0)
5167
+ this.resizeTimeout = setTimeout(() => {
5168
+ this.resizeTimeout = -1;
5169
+ this.view.requestMeasure();
5170
+ }, 50);
5125
5171
  });
5126
5172
  this.resize.observe(view.scrollDOM);
5127
5173
  }
@@ -5145,10 +5191,12 @@ class DOMObserver {
5145
5191
  }, {});
5146
5192
  }
5147
5193
  this.listenForScroll();
5194
+ this.readSelectionRange();
5195
+ this.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange);
5148
5196
  }
5149
5197
  onScroll(e) {
5150
5198
  if (this.intersecting)
5151
- this.flush();
5199
+ this.flush(false);
5152
5200
  this.onScrollChanged(e);
5153
5201
  }
5154
5202
  updateGaps(gaps) {
@@ -5160,8 +5208,8 @@ class DOMObserver {
5160
5208
  }
5161
5209
  }
5162
5210
  onSelectionChange(event) {
5163
- if (this.lastFlush < Date.now() - 50)
5164
- this._selectionRange = null;
5211
+ if (!this.readSelectionRange())
5212
+ return;
5165
5213
  let { view } = this, sel = this.selectionRange;
5166
5214
  if (view.state.facet(editable) ? view.root.activeElement != this.dom : !hasSelection(view.dom, sel))
5167
5215
  return;
@@ -5176,24 +5224,22 @@ class DOMObserver {
5176
5224
  sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset))
5177
5225
  this.flushSoon();
5178
5226
  else
5179
- this.flush();
5180
- }
5181
- get selectionRange() {
5182
- if (!this._selectionRange) {
5183
- let { root } = this.view, sel = getSelection(root);
5184
- // The Selection object is broken in shadow roots in Safari. See
5185
- // https://github.com/codemirror/codemirror.next/issues/414
5186
- if (browser.safari && root.nodeType == 11 && deepActiveElement() == this.view.contentDOM)
5187
- sel = safariSelectionRangeHack(this.view) || sel;
5188
- this._selectionRange = sel;
5189
- }
5190
- return this._selectionRange;
5227
+ this.flush(false);
5228
+ }
5229
+ readSelectionRange() {
5230
+ let { root } = this.view, domSel = getSelection(root);
5231
+ // The Selection object is broken in shadow roots in Safari. See
5232
+ // https://github.com/codemirror/codemirror.next/issues/414
5233
+ let range = browser.safari && root.nodeType == 11 && deepActiveElement() == this.view.contentDOM &&
5234
+ safariSelectionRangeHack(this.view) || domSel;
5235
+ if (this.selectionRange.eq(range))
5236
+ return false;
5237
+ this.selectionRange.setRange(range);
5238
+ return this.selectionChanged = true;
5191
5239
  }
5192
5240
  setSelectionRange(anchor, head) {
5193
- var _a;
5194
- if (!((_a = this._selectionRange) === null || _a === void 0 ? void 0 : _a.type))
5195
- this._selectionRange = { anchorNode: anchor.node, anchorOffset: anchor.offset,
5196
- focusNode: head.node, focusOffset: head.offset };
5241
+ this.selectionRange.set(anchor.node, anchor.offset, head.node, head.offset);
5242
+ this.selectionChanged = false;
5197
5243
  }
5198
5244
  listenForScroll() {
5199
5245
  this.parentCheck = -1;
@@ -5240,7 +5286,6 @@ class DOMObserver {
5240
5286
  if (this.active)
5241
5287
  return;
5242
5288
  this.observer.observe(this.dom, observeOptions);
5243
- this.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange);
5244
5289
  if (useCharData)
5245
5290
  this.dom.addEventListener("DOMCharacterDataModified", this.onCharData);
5246
5291
  this.active = true;
@@ -5250,18 +5295,14 @@ class DOMObserver {
5250
5295
  return;
5251
5296
  this.active = false;
5252
5297
  this.observer.disconnect();
5253
- this.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange);
5254
5298
  if (useCharData)
5255
5299
  this.dom.removeEventListener("DOMCharacterDataModified", this.onCharData);
5256
5300
  }
5257
- clearSelection() {
5258
- this.ignoreSelection.set(this.selectionRange);
5259
- }
5260
5301
  // Throw away any pending changes
5261
5302
  clear() {
5262
5303
  this.observer.takeRecords();
5263
5304
  this.queue.length = 0;
5264
- this.clearSelection();
5305
+ this.selectionChanged = false;
5265
5306
  }
5266
5307
  flushSoon() {
5267
5308
  if (this.delayedFlush < 0)
@@ -5298,24 +5339,24 @@ class DOMObserver {
5298
5339
  return { from, to, typeOver };
5299
5340
  }
5300
5341
  // Apply pending changes, if any
5301
- flush() {
5342
+ flush(readSelection = true) {
5343
+ if (readSelection)
5344
+ this.readSelectionRange();
5302
5345
  // Completely hold off flushing when pending keys are set—the code
5303
5346
  // managing those will make sure processRecords is called and the
5304
5347
  // view is resynchronized after
5305
5348
  if (this.delayedFlush >= 0 || this.view.inputState.pendingAndroidKey)
5306
5349
  return;
5307
- this.lastFlush = Date.now();
5308
5350
  let { from, to, typeOver } = this.processRecords();
5309
- let selection = this.selectionRange;
5310
- let newSel = !this.ignoreSelection.eq(selection) && hasSelection(this.dom, selection);
5351
+ let newSel = this.selectionChanged && hasSelection(this.dom, this.selectionRange);
5311
5352
  if (from < 0 && !newSel)
5312
5353
  return;
5354
+ this.selectionChanged = false;
5313
5355
  let startState = this.view.state;
5314
5356
  this.onChange(from, to, typeOver);
5315
5357
  // The view wasn't updated
5316
5358
  if (this.view.state == startState)
5317
5359
  this.view.docView.reset(newSel);
5318
- this.clearSelection();
5319
5360
  }
5320
5361
  readMutation(rec) {
5321
5362
  let cView = this.view.docView.nearest(rec.target);
@@ -5347,6 +5388,7 @@ class DOMObserver {
5347
5388
  dom.removeEventListener("scroll", this.onScroll);
5348
5389
  window.removeEventListener("scroll", this.onScroll);
5349
5390
  clearTimeout(this.parentCheck);
5391
+ clearTimeout(this.resizeTimeout);
5350
5392
  }
5351
5393
  }
5352
5394
  function findChild(cView, dom, dir) {
@@ -5359,6 +5401,7 @@ function findChild(cView, dom, dir) {
5359
5401
  }
5360
5402
  return null;
5361
5403
  }
5404
+ // Used to work around a Safari Selection/shadow DOM bug (#414)
5362
5405
  function safariSelectionRangeHack(view) {
5363
5406
  let found = null;
5364
5407
  // Because Safari (at least in 2018-2021) doesn't provide regular
@@ -5778,6 +5821,7 @@ class EditorView {
5778
5821
  this.mountStyles();
5779
5822
  this.updateAttrs();
5780
5823
  this.showAnnouncements(transactions);
5824
+ this.docView.updateSelection(redrawn, transactions.some(tr => tr.isUserEvent("select.pointer")));
5781
5825
  }
5782
5826
  finally {
5783
5827
  this.updateState = 0 /* Idle */;
@@ -5863,11 +5907,11 @@ class EditorView {
5863
5907
  for (let i = 0;; i++) {
5864
5908
  this.updateState = 1 /* Measuring */;
5865
5909
  let oldViewport = this.viewport;
5866
- let changed = this.viewState.measure(this.docView, i > 0);
5910
+ let changed = this.viewState.measure(this);
5867
5911
  if (!changed && !this.measureRequests.length && this.viewState.scrollTarget == null)
5868
5912
  break;
5869
5913
  if (i > 5) {
5870
- console.warn("Viewport failed to stabilize");
5914
+ console.warn(this.measureRequests.length ? "Measure loop restarted more than 5 times" : "Viewport failed to stabilize");
5871
5915
  break;
5872
5916
  }
5873
5917
  let measuring = [];
@@ -5883,7 +5927,7 @@ class EditorView {
5883
5927
  return BadMeasure;
5884
5928
  }
5885
5929
  });
5886
- let update = new ViewUpdate(this, this.state);
5930
+ let update = new ViewUpdate(this, this.state), redrawn = false;
5887
5931
  update.flags |= changed;
5888
5932
  if (!updated)
5889
5933
  updated = update;
@@ -5893,14 +5937,15 @@ class EditorView {
5893
5937
  if (!update.empty) {
5894
5938
  this.updatePlugins(update);
5895
5939
  this.inputState.update(update);
5940
+ this.updateAttrs();
5941
+ redrawn = this.docView.update(update);
5896
5942
  }
5897
- this.updateAttrs();
5898
- if (changed)
5899
- this.docView.update(update);
5900
5943
  for (let i = 0; i < measuring.length; i++)
5901
5944
  if (measured[i] != BadMeasure) {
5902
5945
  try {
5903
- measuring[i].write(measured[i], this);
5946
+ let m = measuring[i];
5947
+ if (m.write)
5948
+ m.write(measured[i], this);
5904
5949
  }
5905
5950
  catch (e) {
5906
5951
  logException(this.state, e);
@@ -5910,6 +5955,8 @@ class EditorView {
5910
5955
  this.docView.scrollIntoView(this.viewState.scrollTarget);
5911
5956
  this.viewState.scrollTarget = null;
5912
5957
  }
5958
+ if (redrawn)
5959
+ this.docView.updateSelection(true);
5913
5960
  if (this.viewport.from == oldViewport.from && this.viewport.to == oldViewport.to && this.measureRequests.length == 0)
5914
5961
  break;
5915
5962
  }
@@ -5934,8 +5981,6 @@ class EditorView {
5934
5981
  let editorAttrs = combineAttrs(this.state.facet(editorAttributes), {
5935
5982
  class: "cm-editor" + (this.hasFocus ? " cm-focused " : " ") + this.themeClasses
5936
5983
  });
5937
- updateAttrs(this.dom, this.editorAttrs, editorAttrs);
5938
- this.editorAttrs = editorAttrs;
5939
5984
  let contentAttrs = {
5940
5985
  spellcheck: "false",
5941
5986
  autocorrect: "off",
@@ -5950,7 +5995,11 @@ class EditorView {
5950
5995
  if (this.state.readOnly)
5951
5996
  contentAttrs["aria-readonly"] = "true";
5952
5997
  combineAttrs(this.state.facet(contentAttributes), contentAttrs);
5953
- updateAttrs(this.contentDOM, this.contentAttrs, contentAttrs);
5998
+ this.observer.ignore(() => {
5999
+ updateAttrs(this.contentDOM, this.contentAttrs, contentAttrs);
6000
+ updateAttrs(this.dom, this.editorAttrs, editorAttrs);
6001
+ });
6002
+ this.editorAttrs = editorAttrs;
5954
6003
  this.contentAttrs = contentAttrs;
5955
6004
  }
5956
6005
  showAnnouncements(trs) {
@@ -6020,6 +6069,20 @@ class EditorView {
6020
6069
  return null;
6021
6070
  }
6022
6071
  /**
6072
+ The top position of the document, in screen coordinates. This
6073
+ may be negative when the editor is scrolled down. Points
6074
+ directly to the top of the first line, not above the padding.
6075
+ */
6076
+ get documentTop() {
6077
+ return this.contentDOM.getBoundingClientRect().top + this.viewState.paddingTop;
6078
+ }
6079
+ /**
6080
+ Reports the padding above and below the document.
6081
+ */
6082
+ get documentPadding() {
6083
+ return { top: this.viewState.paddingTop, bottom: this.viewState.paddingBottom };
6084
+ }
6085
+ /**
6023
6086
  Find the line or block widget at the given vertical position.
6024
6087
 
6025
6088
  By default, this position is interpreted as a screen position,
@@ -6029,10 +6092,21 @@ class EditorView {
6029
6092
  position, or a precomputed document top
6030
6093
  (`view.contentDOM.getBoundingClientRect().top`) to limit layout
6031
6094
  queries.
6095
+
6096
+ *Deprecated: use `blockAtHeight` instead.*
6032
6097
  */
6033
6098
  blockAtHeight(height, docTop) {
6099
+ let top = ensureTop(docTop, this);
6100
+ return this.elementAtHeight(height - top).moveY(top);
6101
+ }
6102
+ /**
6103
+ Find the text line or block widget at the given vertical
6104
+ position (which is interpreted as relative to the [top of the
6105
+ document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop)
6106
+ */
6107
+ elementAtHeight(height) {
6034
6108
  this.readMeasured();
6035
- return this.viewState.blockAtHeight(height, ensureTop(docTop, this.contentDOM));
6109
+ return this.viewState.elementAtHeight(height);
6036
6110
  }
6037
6111
  /**
6038
6112
  Find information for the visual line (see
@@ -6044,20 +6118,43 @@ class EditorView {
6044
6118
  Defaults to treating `height` as a screen position. See
6045
6119
  [`blockAtHeight`](https://codemirror.net/6/docs/ref/#view.EditorView.blockAtHeight) for the
6046
6120
  interpretation of the `docTop` parameter.
6121
+
6122
+ *Deprecated: use `lineBlockAtHeight` instead.*
6047
6123
  */
6048
6124
  visualLineAtHeight(height, docTop) {
6125
+ let top = ensureTop(docTop, this);
6126
+ return this.lineBlockAtHeight(height - top).moveY(top);
6127
+ }
6128
+ /**
6129
+ Find the line block (see
6130
+ [`lineBlockAt`](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt) at the given
6131
+ height.
6132
+ */
6133
+ lineBlockAtHeight(height) {
6049
6134
  this.readMeasured();
6050
- return this.viewState.lineAtHeight(height, ensureTop(docTop, this.contentDOM));
6135
+ return this.viewState.lineBlockAtHeight(height);
6051
6136
  }
6052
6137
  /**
6053
6138
  Iterate over the height information of the visual lines in the
6054
6139
  viewport. The heights of lines are reported relative to the
6055
6140
  given document top, which defaults to the screen position of the
6056
6141
  document (forcing a layout).
6142
+
6143
+ *Deprecated: use `viewportLineBlocks` instead.*
6057
6144
  */
6058
6145
  viewportLines(f, docTop) {
6059
- let { from, to } = this.viewport;
6060
- this.viewState.forEachLine(from, to, f, ensureTop(docTop, this.contentDOM));
6146
+ let top = ensureTop(docTop, this);
6147
+ for (let line of this.viewportLineBlocks)
6148
+ f(line.moveY(top));
6149
+ }
6150
+ /**
6151
+ Get the extent and vertical position of all [line
6152
+ blocks](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt) in the viewport. Positions
6153
+ are relative to the [top of the
6154
+ document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop);
6155
+ */
6156
+ get viewportLineBlocks() {
6157
+ return this.viewState.viewportLines;
6061
6158
  }
6062
6159
  /**
6063
6160
  Find the extent and height of the visual line (a range delimited
@@ -6068,9 +6165,22 @@ class EditorView {
6068
6165
  argument, which defaults to 0 for this method. You can pass
6069
6166
  `view.contentDOM.getBoundingClientRect().top` here to get screen
6070
6167
  coordinates.
6168
+
6169
+ *Deprecated: use `lineBlockAt` instead.*
6071
6170
  */
6072
6171
  visualLineAt(pos, docTop = 0) {
6073
- return this.viewState.lineAt(pos, docTop);
6172
+ return this.lineBlockAt(pos).moveY(docTop + this.viewState.paddingTop);
6173
+ }
6174
+ /**
6175
+ Find the line block around the given document position. A line
6176
+ block is a range delimited on both sides by either a
6177
+ non-[hidden](https://codemirror.net/6/docs/ref/#view.Decoration^range) line breaks, or the
6178
+ start/end of the document. It will usually just hold a line of
6179
+ text, but may be broken into multiple textblocks by block
6180
+ widgets.
6181
+ */
6182
+ lineBlockAt(pos) {
6183
+ return this.viewState.lineBlockAt(pos);
6074
6184
  }
6075
6185
  /**
6076
6186
  The editor's total content height.
@@ -6403,8 +6513,9 @@ search match).
6403
6513
  EditorView.announce = state.StateEffect.define();
6404
6514
  // Maximum line length for which we compute accurate bidi info
6405
6515
  const MaxBidiLine = 4096;
6406
- function ensureTop(given, dom) {
6407
- return given == null ? dom.getBoundingClientRect().top : given;
6516
+ // FIXME remove this and its callers on next breaking release
6517
+ function ensureTop(given, view) {
6518
+ return (given == null ? view.contentDOM.getBoundingClientRect().top : given) + view.viewState.paddingTop;
6408
6519
  }
6409
6520
  let resizeDebounce = -1;
6410
6521
  function ensureGlobalHandler() {
@@ -6755,7 +6866,7 @@ function wrappedLine(view, pos, inside) {
6755
6866
  type: exports.BlockType.Text };
6756
6867
  }
6757
6868
  function blockAt(view, pos) {
6758
- let line = view.visualLineAt(pos);
6869
+ let line = view.lineBlockAt(pos);
6759
6870
  if (Array.isArray(line.type))
6760
6871
  for (let l of line.type) {
6761
6872
  if (l.to > pos || l.to == pos && (l.to == line.to || l.type == exports.BlockType.Text))
@@ -7146,7 +7257,7 @@ const activeLineHighlighter = ViewPlugin.fromClass(class {
7146
7257
  for (let r of view.state.selection.ranges) {
7147
7258
  if (!r.empty)
7148
7259
  return Decoration.none;
7149
- let line = view.visualLineAt(r.head);
7260
+ let line = view.lineBlockAt(r.head);
7150
7261
  if (line.from > lastLineStart) {
7151
7262
  deco.push(lineDeco.range(line.from));
7152
7263
  lastLineStart = line.from;