diff .cms/lib/codemirror/src/display/update_display.js @ 0:78edf6b517a0 draft

24.10
author Coffee CMS <info@coffee-cms.ru>
date Fri, 11 Oct 2024 22:40:23 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.cms/lib/codemirror/src/display/update_display.js	Fri Oct 11 22:40:23 2024 +0000
@@ -0,0 +1,267 @@
+import { sawCollapsedSpans } from "../line/saw_special_spans.js"
+import { heightAtLine, visualLineEndNo, visualLineNo } from "../line/spans.js"
+import { getLine, lineNumberFor } from "../line/utils_line.js"
+import { displayHeight, displayWidth, getDimensions, paddingVert, scrollGap } from "../measurement/position_measurement.js"
+import { mac, webkit } from "../util/browser.js"
+import { activeElt, removeChildren, contains, win, root, rootNode } from "../util/dom.js"
+import { hasHandler, signal } from "../util/event.js"
+import { signalLater } from "../util/operation_group.js"
+import { indexOf } from "../util/misc.js"
+
+import { buildLineElement, updateLineForChanges } from "./update_line.js"
+import { startWorker } from "./highlight_worker.js"
+import { maybeUpdateLineNumberWidth } from "./line_numbers.js"
+import { measureForScrollbars, updateScrollbars } from "./scrollbars.js"
+import { updateSelection } from "./selection.js"
+import { updateHeightsInViewport, visibleLines } from "./update_lines.js"
+import { adjustView, countDirtyView, resetView } from "./view_tracking.js"
+
+// DISPLAY DRAWING
+
+export class DisplayUpdate {
+  constructor(cm, viewport, force) {
+    let display = cm.display
+
+    this.viewport = viewport
+    // Store some values that we'll need later (but don't want to force a relayout for)
+    this.visible = visibleLines(display, cm.doc, viewport)
+    this.editorIsHidden = !display.wrapper.offsetWidth
+    this.wrapperHeight = display.wrapper.clientHeight
+    this.wrapperWidth = display.wrapper.clientWidth
+    this.oldDisplayWidth = displayWidth(cm)
+    this.force = force
+    this.dims = getDimensions(cm)
+    this.events = []
+  }
+
+  signal(emitter, type) {
+    if (hasHandler(emitter, type))
+      this.events.push(arguments)
+  }
+  finish() {
+    for (let i = 0; i < this.events.length; i++)
+      signal.apply(null, this.events[i])
+  }
+}
+
+export function maybeClipScrollbars(cm) {
+  let display = cm.display
+  if (!display.scrollbarsClipped && display.scroller.offsetWidth) {
+    display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth
+    display.heightForcer.style.height = scrollGap(cm) + "px"
+    display.sizer.style.marginBottom = -display.nativeBarWidth + "px"
+    display.sizer.style.borderRightWidth = scrollGap(cm) + "px"
+    display.scrollbarsClipped = true
+  }
+}
+
+function selectionSnapshot(cm) {
+  if (cm.hasFocus()) return null
+  let active = activeElt(root(cm))
+  if (!active || !contains(cm.display.lineDiv, active)) return null
+  let result = {activeElt: active}
+  if (window.getSelection) {
+    let sel = win(cm).getSelection()
+    if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) {
+      result.anchorNode = sel.anchorNode
+      result.anchorOffset = sel.anchorOffset
+      result.focusNode = sel.focusNode
+      result.focusOffset = sel.focusOffset
+    }
+  }
+  return result
+}
+
+function restoreSelection(snapshot) {
+  if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt(rootNode(snapshot.activeElt))) return
+  snapshot.activeElt.focus()
+  if (!/^(INPUT|TEXTAREA)$/.test(snapshot.activeElt.nodeName) &&
+      snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) {
+    let doc = snapshot.activeElt.ownerDocument
+    let sel = doc.defaultView.getSelection(), range = doc.createRange()
+    range.setEnd(snapshot.anchorNode, snapshot.anchorOffset)
+    range.collapse(false)
+    sel.removeAllRanges()
+    sel.addRange(range)
+    sel.extend(snapshot.focusNode, snapshot.focusOffset)
+  }
+}
+
+// Does the actual updating of the line display. Bails out
+// (returning false) when there is nothing to be done and forced is
+// false.
+export function updateDisplayIfNeeded(cm, update) {
+  let display = cm.display, doc = cm.doc
+
+  if (update.editorIsHidden) {
+    resetView(cm)
+    return false
+  }
+
+  // Bail out if the visible area is already rendered and nothing changed.
+  if (!update.force &&
+      update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo &&
+      (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) &&
+      display.renderedView == display.view && countDirtyView(cm) == 0)
+    return false
+
+  if (maybeUpdateLineNumberWidth(cm)) {
+    resetView(cm)
+    update.dims = getDimensions(cm)
+  }
+
+  // Compute a suitable new viewport (from & to)
+  let end = doc.first + doc.size
+  let from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first)
+  let to = Math.min(end, update.visible.to + cm.options.viewportMargin)
+  if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom)
+  if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo)
+  if (sawCollapsedSpans) {
+    from = visualLineNo(cm.doc, from)
+    to = visualLineEndNo(cm.doc, to)
+  }
+
+  let different = from != display.viewFrom || to != display.viewTo ||
+    display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth
+  adjustView(cm, from, to)
+
+  display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom))
+  // Position the mover div to align with the current scroll position
+  cm.display.mover.style.top = display.viewOffset + "px"
+
+  let toUpdate = countDirtyView(cm)
+  if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view &&
+      (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo))
+    return false
+
+  // For big changes, we hide the enclosing element during the
+  // update, since that speeds up the operations on most browsers.
+  let selSnapshot = selectionSnapshot(cm)
+  if (toUpdate > 4) display.lineDiv.style.display = "none"
+  patchDisplay(cm, display.updateLineNumbers, update.dims)
+  if (toUpdate > 4) display.lineDiv.style.display = ""
+  display.renderedView = display.view
+  // There might have been a widget with a focused element that got
+  // hidden or updated, if so re-focus it.
+  restoreSelection(selSnapshot)
+
+  // Prevent selection and cursors from interfering with the scroll
+  // width and height.
+  removeChildren(display.cursorDiv)
+  removeChildren(display.selectionDiv)
+  display.gutters.style.height = display.sizer.style.minHeight = 0
+
+  if (different) {
+    display.lastWrapHeight = update.wrapperHeight
+    display.lastWrapWidth = update.wrapperWidth
+    startWorker(cm, 400)
+  }
+
+  display.updateLineNumbers = null
+
+  return true
+}
+
+export function postUpdateDisplay(cm, update) {
+  let viewport = update.viewport
+
+  for (let first = true;; first = false) {
+    if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) {
+      // Clip forced viewport to actual scrollable area.
+      if (viewport && viewport.top != null)
+        viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}
+      // Updated line heights might result in the drawn area not
+      // actually covering the viewport. Keep looping until it does.
+      update.visible = visibleLines(cm.display, cm.doc, viewport)
+      if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo)
+        break
+    } else if (first) {
+      update.visible = visibleLines(cm.display, cm.doc, viewport)
+    }
+    if (!updateDisplayIfNeeded(cm, update)) break
+    updateHeightsInViewport(cm)
+    let barMeasure = measureForScrollbars(cm)
+    updateSelection(cm)
+    updateScrollbars(cm, barMeasure)
+    setDocumentHeight(cm, barMeasure)
+    update.force = false
+  }
+
+  update.signal(cm, "update", cm)
+  if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) {
+    update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo)
+    cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo
+  }
+}
+
+export function updateDisplaySimple(cm, viewport) {
+  let update = new DisplayUpdate(cm, viewport)
+  if (updateDisplayIfNeeded(cm, update)) {
+    updateHeightsInViewport(cm)
+    postUpdateDisplay(cm, update)
+    let barMeasure = measureForScrollbars(cm)
+    updateSelection(cm)
+    updateScrollbars(cm, barMeasure)
+    setDocumentHeight(cm, barMeasure)
+    update.finish()
+  }
+}
+
+// Sync the actual display DOM structure with display.view, removing
+// nodes for lines that are no longer in view, and creating the ones
+// that are not there yet, and updating the ones that are out of
+// date.
+function patchDisplay(cm, updateNumbersFrom, dims) {
+  let display = cm.display, lineNumbers = cm.options.lineNumbers
+  let container = display.lineDiv, cur = container.firstChild
+
+  function rm(node) {
+    let next = node.nextSibling
+    // Works around a throw-scroll bug in OS X Webkit
+    if (webkit && mac && cm.display.currentWheelTarget == node)
+      node.style.display = "none"
+    else
+      node.parentNode.removeChild(node)
+    return next
+  }
+
+  let view = display.view, lineN = display.viewFrom
+  // Loop over the elements in the view, syncing cur (the DOM nodes
+  // in display.lineDiv) with the view as we go.
+  for (let i = 0; i < view.length; i++) {
+    let lineView = view[i]
+    if (lineView.hidden) {
+    } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet
+      let node = buildLineElement(cm, lineView, lineN, dims)
+      container.insertBefore(node, cur)
+    } else { // Already drawn
+      while (cur != lineView.node) cur = rm(cur)
+      let updateNumber = lineNumbers && updateNumbersFrom != null &&
+        updateNumbersFrom <= lineN && lineView.lineNumber
+      if (lineView.changes) {
+        if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false
+        updateLineForChanges(cm, lineView, lineN, dims)
+      }
+      if (updateNumber) {
+        removeChildren(lineView.lineNumber)
+        lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN)))
+      }
+      cur = lineView.node.nextSibling
+    }
+    lineN += lineView.size
+  }
+  while (cur) cur = rm(cur)
+}
+
+export function updateGutterSpace(display) {
+  let width = display.gutters.offsetWidth
+  display.sizer.style.marginLeft = width + "px"
+  // Send an event to consumers responding to changes in gutter width.
+  signalLater(display, "gutterChanged", display)
+}
+
+export function setDocumentHeight(cm, measure) {
+  cm.display.sizer.style.minHeight = measure.docHeight + "px"
+  cm.display.heightForcer.style.top = measure.docHeight + "px"
+  cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px"
+}