@codemirror/view 6.6.0 → 6.7.1

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
@@ -191,6 +191,26 @@ function scrollRectIntoView(dom, rect, side, x, y, xMargin, yMargin, ltr) {
191
191
  }
192
192
  }
193
193
  }
194
+ function scrollableParent(dom) {
195
+ let doc = dom.ownerDocument;
196
+ for (let cur = dom.parentNode; cur;) {
197
+ if (cur == doc.body) {
198
+ break;
199
+ }
200
+ else if (cur.nodeType == 1) {
201
+ if (cur.scrollHeight > cur.clientHeight || cur.scrollWidth > cur.clientWidth)
202
+ return cur;
203
+ cur = cur.assignedSlot || cur.parentNode;
204
+ }
205
+ else if (cur.nodeType == 11) {
206
+ cur = cur.host;
207
+ }
208
+ else {
209
+ break;
210
+ }
211
+ }
212
+ return null;
213
+ }
194
214
  class DOMSelectionState {
195
215
  constructor() {
196
216
  this.anchorNode = null;
@@ -1603,6 +1623,7 @@ class ContentBuilder {
1603
1623
  this.curLine = null;
1604
1624
  this.breakAtStart = 0;
1605
1625
  this.pendingBuffer = 0 /* Buf.No */;
1626
+ this.bufferMarks = [];
1606
1627
  // Set to false directly after a widget that covers the position after it
1607
1628
  this.atCursorPos = true;
1608
1629
  this.openStart = -1;
@@ -1625,20 +1646,20 @@ class ContentBuilder {
1625
1646
  }
1626
1647
  return this.curLine;
1627
1648
  }
1628
- flushBuffer(active) {
1649
+ flushBuffer(active = this.bufferMarks) {
1629
1650
  if (this.pendingBuffer) {
1630
1651
  this.curLine.append(wrapMarks(new WidgetBufferView(-1), active), active.length);
1631
1652
  this.pendingBuffer = 0 /* Buf.No */;
1632
1653
  }
1633
1654
  }
1634
1655
  addBlockWidget(view) {
1635
- this.flushBuffer([]);
1656
+ this.flushBuffer();
1636
1657
  this.curLine = null;
1637
1658
  this.content.push(view);
1638
1659
  }
1639
1660
  finish(openEnd) {
1640
- if (!openEnd)
1641
- this.flushBuffer([]);
1661
+ if (this.pendingBuffer && openEnd <= this.bufferMarks.length)
1662
+ this.flushBuffer();
1642
1663
  else
1643
1664
  this.pendingBuffer = 0 /* Buf.No */;
1644
1665
  if (!this.posCovered())
@@ -1658,8 +1679,9 @@ class ContentBuilder {
1658
1679
  this.content[this.content.length - 1].breakAfter = 1;
1659
1680
  else
1660
1681
  this.breakAtStart = 1;
1661
- this.flushBuffer([]);
1682
+ this.flushBuffer();
1662
1683
  this.curLine = null;
1684
+ this.atCursorPos = true;
1663
1685
  length--;
1664
1686
  continue;
1665
1687
  }
@@ -1701,7 +1723,7 @@ class ContentBuilder {
1701
1723
  else {
1702
1724
  let view = WidgetView.create(deco.widget || new NullWidget("span"), len, len ? 0 : deco.startSide);
1703
1725
  let cursorBefore = this.atCursorPos && !view.isEditable && openStart <= active.length && (from < to || deco.startSide > 0);
1704
- let cursorAfter = !view.isEditable && (from < to || deco.startSide <= 0);
1726
+ let cursorAfter = !view.isEditable && (from < to || openStart > active.length || deco.startSide <= 0);
1705
1727
  let line = this.getLine();
1706
1728
  if (this.pendingBuffer == 2 /* Buf.IfCursor */ && !cursorBefore)
1707
1729
  this.pendingBuffer = 0 /* Buf.No */;
@@ -1712,7 +1734,9 @@ class ContentBuilder {
1712
1734
  }
1713
1735
  line.append(wrapMarks(view, active), openStart);
1714
1736
  this.atCursorPos = cursorAfter;
1715
- this.pendingBuffer = !cursorAfter ? 0 /* Buf.No */ : from < to ? 1 /* Buf.Yes */ : 2 /* Buf.IfCursor */;
1737
+ this.pendingBuffer = !cursorAfter ? 0 /* Buf.No */ : from < to || openStart > active.length ? 1 /* Buf.Yes */ : 2 /* Buf.IfCursor */;
1738
+ if (this.pendingBuffer)
1739
+ this.bufferMarks = active.slice();
1716
1740
  }
1717
1741
  }
1718
1742
  else if (this.doc.lineAt(this.pos).from == this.pos) { // Line decoration
@@ -3387,22 +3411,30 @@ class InputState {
3387
3411
  this.compositionFirstChange = null;
3388
3412
  this.compositionEndedAt = 0;
3389
3413
  this.mouseSelection = null;
3414
+ let handleEvent = (handler, event) => {
3415
+ if (this.ignoreDuringComposition(event))
3416
+ return;
3417
+ if (event.type == "keydown" && this.keydown(view, event))
3418
+ return;
3419
+ if (this.mustFlushObserver(event))
3420
+ view.observer.forceFlush();
3421
+ if (this.runCustomHandlers(event.type, view, event))
3422
+ event.preventDefault();
3423
+ else
3424
+ handler(view, event);
3425
+ };
3390
3426
  for (let type in handlers) {
3391
3427
  let handler = handlers[type];
3392
- view.contentDOM.addEventListener(type, (event) => {
3393
- if (!eventBelongsToEditor(view, event) || this.ignoreDuringComposition(event))
3394
- return;
3395
- if (type == "keydown" && this.keydown(view, event))
3396
- return;
3397
- if (this.mustFlushObserver(event))
3398
- view.observer.forceFlush();
3399
- if (this.runCustomHandlers(type, view, event))
3400
- event.preventDefault();
3401
- else
3402
- handler(view, event);
3428
+ view.contentDOM.addEventListener(type, event => {
3429
+ if (eventBelongsToEditor(view, event))
3430
+ handleEvent(handler, event);
3403
3431
  }, handlerOptions[type]);
3404
3432
  this.registeredEvents.push(type);
3405
3433
  }
3434
+ view.scrollDOM.addEventListener("mousedown", (event) => {
3435
+ if (event.target == view.scrollDOM)
3436
+ handleEvent(handlers.mousedown, event);
3437
+ });
3406
3438
  if (browser.chrome && browser.chrome_version == 102) { // FIXME remove at some point
3407
3439
  // On Chrome 102, viewport updates somehow stop wheel-based
3408
3440
  // scrolling. Turning off pointer events during the scroll seems
@@ -3559,12 +3591,18 @@ const PendingKeys = [
3559
3591
  const EmacsyPendingKeys = "dthko";
3560
3592
  // Key codes for modifier keys
3561
3593
  const modifierCodes = [16, 17, 18, 20, 91, 92, 224, 225];
3594
+ function dragScrollSpeed(dist) {
3595
+ return dist * 0.7 + 8;
3596
+ }
3562
3597
  class MouseSelection {
3563
3598
  constructor(view, startEvent, style, mustSelect) {
3564
3599
  this.view = view;
3565
3600
  this.style = style;
3566
3601
  this.mustSelect = mustSelect;
3602
+ this.scrollSpeed = { x: 0, y: 0 };
3603
+ this.scrolling = -1;
3567
3604
  this.lastEvent = startEvent;
3605
+ this.scrollParent = scrollableParent(view.contentDOM);
3568
3606
  let doc = view.contentDOM.ownerDocument;
3569
3607
  doc.addEventListener("mousemove", this.move = this.move.bind(this));
3570
3608
  doc.addEventListener("mouseup", this.up = this.up.bind(this));
@@ -3580,11 +3618,24 @@ class MouseSelection {
3580
3618
  }
3581
3619
  }
3582
3620
  move(event) {
3621
+ var _a;
3583
3622
  if (event.buttons == 0)
3584
3623
  return this.destroy();
3585
3624
  if (this.dragging !== false)
3586
3625
  return;
3587
3626
  this.select(this.lastEvent = event);
3627
+ let sx = 0, sy = 0;
3628
+ let rect = ((_a = this.scrollParent) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect())
3629
+ || { left: 0, top: 0, right: this.view.win.innerWidth, bottom: this.view.win.innerHeight };
3630
+ if (event.clientX <= rect.left)
3631
+ sx = -dragScrollSpeed(rect.left - event.clientX);
3632
+ else if (event.clientX >= rect.right)
3633
+ sx = dragScrollSpeed(event.clientX - rect.right);
3634
+ if (event.clientY <= rect.top)
3635
+ sy = -dragScrollSpeed(rect.top - event.clientY);
3636
+ else if (event.clientY >= rect.bottom)
3637
+ sy = dragScrollSpeed(event.clientY - rect.bottom);
3638
+ this.setScrollSpeed(sx, sy);
3588
3639
  }
3589
3640
  up(event) {
3590
3641
  if (this.dragging == null)
@@ -3594,19 +3645,41 @@ class MouseSelection {
3594
3645
  this.destroy();
3595
3646
  }
3596
3647
  destroy() {
3648
+ this.setScrollSpeed(0, 0);
3597
3649
  let doc = this.view.contentDOM.ownerDocument;
3598
3650
  doc.removeEventListener("mousemove", this.move);
3599
3651
  doc.removeEventListener("mouseup", this.up);
3600
3652
  this.view.inputState.mouseSelection = null;
3601
3653
  }
3654
+ setScrollSpeed(sx, sy) {
3655
+ this.scrollSpeed = { x: sx, y: sy };
3656
+ if (sx || sy) {
3657
+ if (this.scrolling < 0)
3658
+ this.scrolling = setInterval(() => this.scroll(), 50);
3659
+ }
3660
+ else if (this.scrolling > -1) {
3661
+ clearInterval(this.scrolling);
3662
+ this.scrolling = -1;
3663
+ }
3664
+ }
3665
+ scroll() {
3666
+ if (this.scrollParent) {
3667
+ this.scrollParent.scrollLeft += this.scrollSpeed.x;
3668
+ this.scrollParent.scrollTop += this.scrollSpeed.y;
3669
+ }
3670
+ else {
3671
+ this.view.win.scrollBy(this.scrollSpeed.x, this.scrollSpeed.y);
3672
+ }
3673
+ if (this.dragging === false)
3674
+ this.select(this.lastEvent);
3675
+ }
3602
3676
  select(event) {
3603
3677
  let selection = this.style.get(event, this.extend, this.multiple);
3604
3678
  if (this.mustSelect || !selection.eq(this.view.state.selection) ||
3605
3679
  selection.main.assoc != this.view.state.selection.main.assoc)
3606
3680
  this.view.dispatch({
3607
3681
  selection,
3608
- userEvent: "select.pointer",
3609
- scrollIntoView: true
3682
+ userEvent: "select.pointer"
3610
3683
  });
3611
3684
  this.mustSelect = false;
3612
3685
  }
@@ -3797,23 +3870,15 @@ function getClickType(event) {
3797
3870
  function basicMouseSelection(view, event) {
3798
3871
  let start = queryPos(view, event), type = getClickType(event);
3799
3872
  let startSel = view.state.selection;
3800
- let last = start, lastEvent = event;
3801
3873
  return {
3802
3874
  update(update) {
3803
3875
  if (update.docChanged) {
3804
3876
  start.pos = update.changes.mapPos(start.pos);
3805
3877
  startSel = startSel.map(update.changes);
3806
- lastEvent = null;
3807
3878
  }
3808
3879
  },
3809
3880
  get(event, extend, multiple) {
3810
- let cur;
3811
- if (lastEvent && event.clientX == lastEvent.clientX && event.clientY == lastEvent.clientY)
3812
- cur = last;
3813
- else {
3814
- cur = last = queryPos(view, event);
3815
- lastEvent = event;
3816
- }
3881
+ let cur = queryPos(view, event);
3817
3882
  let range = rangeForClick(view, cur.pos, cur.bias, type);
3818
3883
  if (start.pos != cur.pos && !extend) {
3819
3884
  let startRange = rangeForClick(view, start.pos, start.bias, type);
@@ -5291,7 +5356,6 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
5291
5356
  margin: 0,
5292
5357
  flexGrow: 2,
5293
5358
  flexShrink: 0,
5294
- minHeight: "100%",
5295
5359
  display: "block",
5296
5360
  whiteSpace: "pre",
5297
5361
  wordWrap: "normal",
@@ -5438,6 +5502,21 @@ const baseTheme$1 = buildTheme("." + baseThemeID, {
5438
5502
  display: "inline-block",
5439
5503
  verticalAlign: "top",
5440
5504
  },
5505
+ ".cm-highlightSpace:before": {
5506
+ content: "attr(data-display)",
5507
+ position: "absolute",
5508
+ pointerEvents: "none",
5509
+ color: "#888"
5510
+ },
5511
+ ".cm-highlightTab": {
5512
+ backgroundImage: `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="200" height="20"><path stroke="%23888" stroke-width="1" fill="none" d="M1 10H196L190 5M190 15L196 10M197 4L197 16"/></svg>')`,
5513
+ backgroundSize: "auto 100%",
5514
+ backgroundPosition: "right 90%",
5515
+ backgroundRepeat: "no-repeat"
5516
+ },
5517
+ ".cm-trailingSpace": {
5518
+ backgroundColor: "#ff332255"
5519
+ },
5441
5520
  ".cm-button": {
5442
5521
  verticalAlign: "middle",
5443
5522
  color: "inherit",
@@ -5740,7 +5819,8 @@ class DOMObserver {
5740
5819
  this.lastChange = 0;
5741
5820
  this.scrollTargets = [];
5742
5821
  this.intersection = null;
5743
- this.resize = null;
5822
+ this.resizeScroll = null;
5823
+ this.resizeContent = null;
5744
5824
  this.intersecting = false;
5745
5825
  this.gapIntersection = null;
5746
5826
  this.gaps = [];
@@ -5778,12 +5858,14 @@ class DOMObserver {
5778
5858
  this.onPrint = this.onPrint.bind(this);
5779
5859
  this.onScroll = this.onScroll.bind(this);
5780
5860
  if (typeof ResizeObserver == "function") {
5781
- this.resize = new ResizeObserver(() => {
5861
+ this.resizeScroll = new ResizeObserver(() => {
5782
5862
  var _a;
5783
5863
  if (((_a = this.view.docView) === null || _a === void 0 ? void 0 : _a.lastUpdate) < Date.now() - 75)
5784
5864
  this.onResize();
5785
5865
  });
5786
- this.resize.observe(view.scrollDOM);
5866
+ this.resizeScroll.observe(view.scrollDOM);
5867
+ this.resizeContent = new ResizeObserver(() => this.view.requestMeasure());
5868
+ this.resizeContent.observe(view.contentDOM);
5787
5869
  }
5788
5870
  this.addWindowListeners(this.win = view.win);
5789
5871
  this.start();
@@ -6102,11 +6184,12 @@ class DOMObserver {
6102
6184
  win.document.removeEventListener("selectionchange", this.onSelectionChange);
6103
6185
  }
6104
6186
  destroy() {
6105
- var _a, _b, _c;
6187
+ var _a, _b, _c, _d;
6106
6188
  this.stop();
6107
6189
  (_a = this.intersection) === null || _a === void 0 ? void 0 : _a.disconnect();
6108
6190
  (_b = this.gapIntersection) === null || _b === void 0 ? void 0 : _b.disconnect();
6109
- (_c = this.resize) === null || _c === void 0 ? void 0 : _c.disconnect();
6191
+ (_c = this.resizeScroll) === null || _c === void 0 ? void 0 : _c.disconnect();
6192
+ (_d = this.resizeContent) === null || _d === void 0 ? void 0 : _d.disconnect();
6110
6193
  for (let dom of this.scrollTargets)
6111
6194
  dom.removeEventListener("scroll", this.onScroll);
6112
6195
  this.removeWindowListeners(this.win);
@@ -6205,7 +6288,7 @@ class EditorView {
6205
6288
  this.scrollDOM.className = "cm-scroller";
6206
6289
  this.scrollDOM.appendChild(this.contentDOM);
6207
6290
  this.announceDOM = document.createElement("div");
6208
- this.announceDOM.style.cssText = "position: absolute; top: -10000px";
6291
+ this.announceDOM.style.cssText = "position: fixed; top: -10000px";
6209
6292
  this.announceDOM.setAttribute("aria-live", "polite");
6210
6293
  this.dom = document.createElement("div");
6211
6294
  this.dom.appendChild(this.announceDOM);
@@ -7296,7 +7379,8 @@ a rectangle at a given set of coordinates.
7296
7379
  */
7297
7380
  class RectangleMarker {
7298
7381
  /**
7299
- Create a marker with the given class and dimensions.
7382
+ Create a marker with the given class and dimensions. If `width`
7383
+ is null, the DOM element will get no width style.
7300
7384
  */
7301
7385
  constructor(className, left, top, width, height) {
7302
7386
  this.className = className;
@@ -7320,7 +7404,7 @@ class RectangleMarker {
7320
7404
  adjust(elt) {
7321
7405
  elt.style.left = this.left + "px";
7322
7406
  elt.style.top = this.top + "px";
7323
- if (this.width >= 0)
7407
+ if (this.width != null)
7324
7408
  elt.style.width = this.width + "px";
7325
7409
  elt.style.height = this.height + "px";
7326
7410
  }
@@ -7328,6 +7412,129 @@ class RectangleMarker {
7328
7412
  return this.left == p.left && this.top == p.top && this.width == p.width && this.height == p.height &&
7329
7413
  this.className == p.className;
7330
7414
  }
7415
+ /**
7416
+ Create a set of rectangles for the given selection range,
7417
+ assigning them theclass`className`. Will create a single
7418
+ rectangle for empty ranges, and a set of selection-style
7419
+ rectangles covering the range's content (in a bidi-aware
7420
+ way) for non-empty ones.
7421
+ */
7422
+ static forRange(view, className, range) {
7423
+ if (range.empty) {
7424
+ let pos = view.coordsAtPos(range.head, range.assoc || 1);
7425
+ if (!pos)
7426
+ return [];
7427
+ let base = getBase(view);
7428
+ return [new RectangleMarker(className, pos.left - base.left, pos.top - base.top, null, pos.bottom - pos.top)];
7429
+ }
7430
+ else {
7431
+ return rectanglesForRange(view, className, range);
7432
+ }
7433
+ }
7434
+ }
7435
+ function getBase(view) {
7436
+ let rect = view.scrollDOM.getBoundingClientRect();
7437
+ let left = view.textDirection == exports.Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth;
7438
+ return { left: left - view.scrollDOM.scrollLeft, top: rect.top - view.scrollDOM.scrollTop };
7439
+ }
7440
+ function wrappedLine(view, pos, inside) {
7441
+ let range = state.EditorSelection.cursor(pos);
7442
+ return { from: Math.max(inside.from, view.moveToLineBoundary(range, false, true).from),
7443
+ to: Math.min(inside.to, view.moveToLineBoundary(range, true, true).from),
7444
+ type: exports.BlockType.Text };
7445
+ }
7446
+ function blockAt(view, pos) {
7447
+ let line = view.lineBlockAt(pos);
7448
+ if (Array.isArray(line.type))
7449
+ for (let l of line.type) {
7450
+ if (l.to > pos || l.to == pos && (l.to == line.to || l.type == exports.BlockType.Text))
7451
+ return l;
7452
+ }
7453
+ return line;
7454
+ }
7455
+ function rectanglesForRange(view, className, range) {
7456
+ if (range.to <= view.viewport.from || range.from >= view.viewport.to)
7457
+ return [];
7458
+ let from = Math.max(range.from, view.viewport.from), to = Math.min(range.to, view.viewport.to);
7459
+ let ltr = view.textDirection == exports.Direction.LTR;
7460
+ let content = view.contentDOM, contentRect = content.getBoundingClientRect(), base = getBase(view);
7461
+ let lineStyle = window.getComputedStyle(content.firstChild);
7462
+ let leftSide = contentRect.left + parseInt(lineStyle.paddingLeft) + Math.min(0, parseInt(lineStyle.textIndent));
7463
+ let rightSide = contentRect.right - parseInt(lineStyle.paddingRight);
7464
+ let startBlock = blockAt(view, from), endBlock = blockAt(view, to);
7465
+ let visualStart = startBlock.type == exports.BlockType.Text ? startBlock : null;
7466
+ let visualEnd = endBlock.type == exports.BlockType.Text ? endBlock : null;
7467
+ if (view.lineWrapping) {
7468
+ if (visualStart)
7469
+ visualStart = wrappedLine(view, from, visualStart);
7470
+ if (visualEnd)
7471
+ visualEnd = wrappedLine(view, to, visualEnd);
7472
+ }
7473
+ if (visualStart && visualEnd && visualStart.from == visualEnd.from) {
7474
+ return pieces(drawForLine(range.from, range.to, visualStart));
7475
+ }
7476
+ else {
7477
+ let top = visualStart ? drawForLine(range.from, null, visualStart) : drawForWidget(startBlock, false);
7478
+ let bottom = visualEnd ? drawForLine(null, range.to, visualEnd) : drawForWidget(endBlock, true);
7479
+ let between = [];
7480
+ if ((visualStart || startBlock).to < (visualEnd || endBlock).from - 1)
7481
+ between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
7482
+ else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == exports.BlockType.Text)
7483
+ top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
7484
+ return pieces(top).concat(between).concat(pieces(bottom));
7485
+ }
7486
+ function piece(left, top, right, bottom) {
7487
+ return new RectangleMarker(className, left - base.left, top - base.top - 0.01 /* C.Epsilon */, right - left, bottom - top + 0.01 /* C.Epsilon */);
7488
+ }
7489
+ function pieces({ top, bottom, horizontal }) {
7490
+ let pieces = [];
7491
+ for (let i = 0; i < horizontal.length; i += 2)
7492
+ pieces.push(piece(horizontal[i], top, horizontal[i + 1], bottom));
7493
+ return pieces;
7494
+ }
7495
+ // Gets passed from/to in line-local positions
7496
+ function drawForLine(from, to, line) {
7497
+ let top = 1e9, bottom = -1e9, horizontal = [];
7498
+ function addSpan(from, fromOpen, to, toOpen, dir) {
7499
+ // Passing 2/-2 is a kludge to force the view to return
7500
+ // coordinates on the proper side of block widgets, since
7501
+ // normalizing the side there, though appropriate for most
7502
+ // coordsAtPos queries, would break selection drawing.
7503
+ let fromCoords = view.coordsAtPos(from, (from == line.to ? -2 : 2));
7504
+ let toCoords = view.coordsAtPos(to, (to == line.from ? 2 : -2));
7505
+ top = Math.min(fromCoords.top, toCoords.top, top);
7506
+ bottom = Math.max(fromCoords.bottom, toCoords.bottom, bottom);
7507
+ if (dir == exports.Direction.LTR)
7508
+ horizontal.push(ltr && fromOpen ? leftSide : fromCoords.left, ltr && toOpen ? rightSide : toCoords.right);
7509
+ else
7510
+ horizontal.push(!ltr && toOpen ? leftSide : toCoords.left, !ltr && fromOpen ? rightSide : fromCoords.right);
7511
+ }
7512
+ let start = from !== null && from !== void 0 ? from : line.from, end = to !== null && to !== void 0 ? to : line.to;
7513
+ // Split the range by visible range and document line
7514
+ for (let r of view.visibleRanges)
7515
+ if (r.to > start && r.from < end) {
7516
+ for (let pos = Math.max(r.from, start), endPos = Math.min(r.to, end);;) {
7517
+ let docLine = view.state.doc.lineAt(pos);
7518
+ for (let span of view.bidiSpans(docLine)) {
7519
+ let spanFrom = span.from + docLine.from, spanTo = span.to + docLine.from;
7520
+ if (spanFrom >= endPos)
7521
+ break;
7522
+ if (spanTo > pos)
7523
+ addSpan(Math.max(spanFrom, pos), from == null && spanFrom <= start, Math.min(spanTo, endPos), to == null && spanTo >= end, span.dir);
7524
+ }
7525
+ pos = docLine.to + 1;
7526
+ if (pos >= endPos)
7527
+ break;
7528
+ }
7529
+ }
7530
+ if (horizontal.length == 0)
7531
+ addSpan(start, from == null, end, to == null, view.textDirection);
7532
+ return { top, bottom, horizontal };
7533
+ }
7534
+ function drawForWidget(block, top) {
7535
+ let y = contentRect.top + (top ? block.top : block.bottom);
7536
+ return { top: y, bottom: y, horizontal: [] };
7537
+ }
7331
7538
  }
7332
7539
  function sameMarker(a, b) {
7333
7540
  return a.constructor == b.constructor && a.eq(b);
@@ -7387,6 +7594,8 @@ class LayerView {
7387
7594
  }
7388
7595
  }
7389
7596
  destroy() {
7597
+ if (this.layer.destroy)
7598
+ this.layer.destroy(this.dom, this.view);
7390
7599
  this.dom.remove();
7391
7600
  }
7392
7601
  }
@@ -7446,13 +7655,14 @@ function configChanged(update) {
7446
7655
  const cursorLayer = layer({
7447
7656
  above: true,
7448
7657
  markers(view) {
7449
- let { state } = view, conf = state.facet(selectionConfig);
7658
+ let { state: state$1 } = view, conf = state$1.facet(selectionConfig);
7450
7659
  let cursors = [];
7451
- for (let r of state.selection.ranges) {
7452
- let prim = r == state.selection.main;
7660
+ for (let r of state$1.selection.ranges) {
7661
+ let prim = r == state$1.selection.main;
7453
7662
  if (r.empty ? !prim || CanHidePrimary : conf.drawRangeCursor) {
7454
- let piece = measureCursor(view, r, prim);
7455
- if (piece)
7663
+ let className = prim ? "cm-cursor cm-cursor-primary" : "cm-cursor cm-cursor-secondary";
7664
+ let cursor = r.empty ? r : state.EditorSelection.cursor(r.head, r.head > r.anchor ? -1 : 1);
7665
+ for (let piece of RectangleMarker.forRange(view, className, cursor))
7456
7666
  cursors.push(piece);
7457
7667
  }
7458
7668
  }
@@ -7477,7 +7687,8 @@ function setBlinkRate(state, dom) {
7477
7687
  const selectionLayer = layer({
7478
7688
  above: false,
7479
7689
  markers(view) {
7480
- return view.state.selection.ranges.map(r => r.empty ? [] : measureRange(view, r)).reduce((a, b) => a.concat(b));
7690
+ return view.state.selection.ranges.map(r => r.empty ? [] : RectangleMarker.forRange(view, "cm-selectionBackground", r))
7691
+ .reduce((a, b) => a.concat(b));
7481
7692
  },
7482
7693
  update(update, dom) {
7483
7694
  return update.docChanged || update.selectionSet || update.viewportChanged || configChanged(update);
@@ -7493,117 +7704,6 @@ const themeSpec = {
7493
7704
  if (CanHidePrimary)
7494
7705
  themeSpec[".cm-line"].caretColor = "transparent !important";
7495
7706
  const hideNativeSelection = state.Prec.highest(EditorView.theme(themeSpec));
7496
- function getBase(view) {
7497
- let rect = view.scrollDOM.getBoundingClientRect();
7498
- let left = view.textDirection == exports.Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth;
7499
- return { left: left - view.scrollDOM.scrollLeft, top: rect.top - view.scrollDOM.scrollTop };
7500
- }
7501
- function wrappedLine(view, pos, inside) {
7502
- let range = state.EditorSelection.cursor(pos);
7503
- return { from: Math.max(inside.from, view.moveToLineBoundary(range, false, true).from),
7504
- to: Math.min(inside.to, view.moveToLineBoundary(range, true, true).from),
7505
- type: exports.BlockType.Text };
7506
- }
7507
- function blockAt(view, pos) {
7508
- let line = view.lineBlockAt(pos);
7509
- if (Array.isArray(line.type))
7510
- for (let l of line.type) {
7511
- if (l.to > pos || l.to == pos && (l.to == line.to || l.type == exports.BlockType.Text))
7512
- return l;
7513
- }
7514
- return line;
7515
- }
7516
- function measureRange(view, range) {
7517
- if (range.to <= view.viewport.from || range.from >= view.viewport.to)
7518
- return [];
7519
- let from = Math.max(range.from, view.viewport.from), to = Math.min(range.to, view.viewport.to);
7520
- let ltr = view.textDirection == exports.Direction.LTR;
7521
- let content = view.contentDOM, contentRect = content.getBoundingClientRect(), base = getBase(view);
7522
- let lineStyle = window.getComputedStyle(content.firstChild);
7523
- let leftSide = contentRect.left + parseInt(lineStyle.paddingLeft) + Math.min(0, parseInt(lineStyle.textIndent));
7524
- let rightSide = contentRect.right - parseInt(lineStyle.paddingRight);
7525
- let startBlock = blockAt(view, from), endBlock = blockAt(view, to);
7526
- let visualStart = startBlock.type == exports.BlockType.Text ? startBlock : null;
7527
- let visualEnd = endBlock.type == exports.BlockType.Text ? endBlock : null;
7528
- if (view.lineWrapping) {
7529
- if (visualStart)
7530
- visualStart = wrappedLine(view, from, visualStart);
7531
- if (visualEnd)
7532
- visualEnd = wrappedLine(view, to, visualEnd);
7533
- }
7534
- if (visualStart && visualEnd && visualStart.from == visualEnd.from) {
7535
- return pieces(drawForLine(range.from, range.to, visualStart));
7536
- }
7537
- else {
7538
- let top = visualStart ? drawForLine(range.from, null, visualStart) : drawForWidget(startBlock, false);
7539
- let bottom = visualEnd ? drawForLine(null, range.to, visualEnd) : drawForWidget(endBlock, true);
7540
- let between = [];
7541
- if ((visualStart || startBlock).to < (visualEnd || endBlock).from - 1)
7542
- between.push(piece(leftSide, top.bottom, rightSide, bottom.top));
7543
- else if (top.bottom < bottom.top && view.elementAtHeight((top.bottom + bottom.top) / 2).type == exports.BlockType.Text)
7544
- top.bottom = bottom.top = (top.bottom + bottom.top) / 2;
7545
- return pieces(top).concat(between).concat(pieces(bottom));
7546
- }
7547
- function piece(left, top, right, bottom) {
7548
- return new RectangleMarker("cm-selectionBackground", left - base.left, top - base.top - 0.01 /* C.Epsilon */, right - left, bottom - top + 0.01 /* C.Epsilon */);
7549
- }
7550
- function pieces({ top, bottom, horizontal }) {
7551
- let pieces = [];
7552
- for (let i = 0; i < horizontal.length; i += 2)
7553
- pieces.push(piece(horizontal[i], top, horizontal[i + 1], bottom));
7554
- return pieces;
7555
- }
7556
- // Gets passed from/to in line-local positions
7557
- function drawForLine(from, to, line) {
7558
- let top = 1e9, bottom = -1e9, horizontal = [];
7559
- function addSpan(from, fromOpen, to, toOpen, dir) {
7560
- // Passing 2/-2 is a kludge to force the view to return
7561
- // coordinates on the proper side of block widgets, since
7562
- // normalizing the side there, though appropriate for most
7563
- // coordsAtPos queries, would break selection drawing.
7564
- let fromCoords = view.coordsAtPos(from, (from == line.to ? -2 : 2));
7565
- let toCoords = view.coordsAtPos(to, (to == line.from ? 2 : -2));
7566
- top = Math.min(fromCoords.top, toCoords.top, top);
7567
- bottom = Math.max(fromCoords.bottom, toCoords.bottom, bottom);
7568
- if (dir == exports.Direction.LTR)
7569
- horizontal.push(ltr && fromOpen ? leftSide : fromCoords.left, ltr && toOpen ? rightSide : toCoords.right);
7570
- else
7571
- horizontal.push(!ltr && toOpen ? leftSide : toCoords.left, !ltr && fromOpen ? rightSide : fromCoords.right);
7572
- }
7573
- let start = from !== null && from !== void 0 ? from : line.from, end = to !== null && to !== void 0 ? to : line.to;
7574
- // Split the range by visible range and document line
7575
- for (let r of view.visibleRanges)
7576
- if (r.to > start && r.from < end) {
7577
- for (let pos = Math.max(r.from, start), endPos = Math.min(r.to, end);;) {
7578
- let docLine = view.state.doc.lineAt(pos);
7579
- for (let span of view.bidiSpans(docLine)) {
7580
- let spanFrom = span.from + docLine.from, spanTo = span.to + docLine.from;
7581
- if (spanFrom >= endPos)
7582
- break;
7583
- if (spanTo > pos)
7584
- addSpan(Math.max(spanFrom, pos), from == null && spanFrom <= start, Math.min(spanTo, endPos), to == null && spanTo >= end, span.dir);
7585
- }
7586
- pos = docLine.to + 1;
7587
- if (pos >= endPos)
7588
- break;
7589
- }
7590
- }
7591
- if (horizontal.length == 0)
7592
- addSpan(start, from == null, end, to == null, view.textDirection);
7593
- return { top, bottom, horizontal };
7594
- }
7595
- function drawForWidget(block, top) {
7596
- let y = contentRect.top + (top ? block.top : block.bottom);
7597
- return { top: y, bottom: y, horizontal: [] };
7598
- }
7599
- }
7600
- function measureCursor(view, cursor, primary) {
7601
- let pos = view.coordsAtPos(cursor.head, cursor.assoc || 1);
7602
- if (!pos)
7603
- return null;
7604
- let base = getBase(view);
7605
- return new RectangleMarker(primary ? "cm-cursor cm-cursor-primary" : "cm-cursor cm-cursor-secondary", pos.left - base.left, pos.top - base.top, -1, pos.bottom - pos.top);
7606
- }
7607
7707
 
7608
7708
  const setDropCursorPos = state.StateEffect.define({
7609
7709
  map(pos, mapping) { return pos == null ? null : mapping.mapPos(pos); }
@@ -9370,6 +9470,57 @@ function highlightActiveLineGutter() {
9370
9470
  return activeLineGutterHighlighter;
9371
9471
  }
9372
9472
 
9473
+ const WhitespaceDeco = new Map();
9474
+ function getWhitespaceDeco(space) {
9475
+ let deco = WhitespaceDeco.get(space);
9476
+ if (!deco)
9477
+ WhitespaceDeco.set(space, deco = Decoration.mark({
9478
+ attributes: space === "\t" ? {
9479
+ class: "cm-highlightTab",
9480
+ } : {
9481
+ class: "cm-highlightSpace",
9482
+ "data-display": space.replace(/ /g, "·")
9483
+ }
9484
+ }));
9485
+ return deco;
9486
+ }
9487
+ function matcher(decorator) {
9488
+ return ViewPlugin.define(view => ({
9489
+ decorations: decorator.createDeco(view),
9490
+ update(u) {
9491
+ this.decorations = decorator.updateDeco(u, this.decorations);
9492
+ },
9493
+ }), {
9494
+ decorations: v => v.decorations
9495
+ });
9496
+ }
9497
+ const whitespaceHighlighter = matcher(new MatchDecorator({
9498
+ regexp: /\t| +/g,
9499
+ decoration: match => getWhitespaceDeco(match[0]),
9500
+ boundary: /\S/,
9501
+ }));
9502
+ /**
9503
+ Returns an extension that highlights whitespace, adding a
9504
+ `cm-highlightSpace` class to stretches of spaces, and a
9505
+ `cm-highlightTab` class to individual tab characters. By default,
9506
+ the former are shown as faint dots, and the latter as arrows.
9507
+ */
9508
+ function highlightWhitespace() {
9509
+ return whitespaceHighlighter;
9510
+ }
9511
+ const trailingHighlighter = matcher(new MatchDecorator({
9512
+ regexp: /\s+$/g,
9513
+ decoration: Decoration.mark({ class: "cm-trailingSpace" }),
9514
+ boundary: /\S/,
9515
+ }));
9516
+ /**
9517
+ Returns an extension that adds a `cm-trailingSpace` class to all
9518
+ trailing whitespace.
9519
+ */
9520
+ function highlightTrailingWhitespace() {
9521
+ return trailingHighlighter;
9522
+ }
9523
+
9373
9524
  /**
9374
9525
  @internal
9375
9526
  */
@@ -9399,6 +9550,8 @@ exports.hasHoverTooltips = hasHoverTooltips;
9399
9550
  exports.highlightActiveLine = highlightActiveLine;
9400
9551
  exports.highlightActiveLineGutter = highlightActiveLineGutter;
9401
9552
  exports.highlightSpecialChars = highlightSpecialChars;
9553
+ exports.highlightTrailingWhitespace = highlightTrailingWhitespace;
9554
+ exports.highlightWhitespace = highlightWhitespace;
9402
9555
  exports.hoverTooltip = hoverTooltip;
9403
9556
  exports.keymap = keymap;
9404
9557
  exports.layer = layer;