@codemirror/view 0.19.18 → 0.19.22

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