diff .cms/lib/codemirror/src/display/operations.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/operations.js	Fri Oct 11 22:40:23 2024 +0000
@@ -0,0 +1,206 @@
+import { clipPos } from "../line/pos.js"
+import { findMaxLine } from "../line/spans.js"
+import { displayWidth, measureChar, scrollGap } from "../measurement/position_measurement.js"
+import { signal } from "../util/event.js"
+import { activeElt, root } from "../util/dom.js"
+import { finishOperation, pushOperation } from "../util/operation_group.js"
+
+import { ensureFocus } from "./focus.js"
+import { measureForScrollbars, updateScrollbars } from "./scrollbars.js"
+import { restartBlink } from "./selection.js"
+import { maybeScrollWindow, scrollPosIntoView, setScrollLeft, setScrollTop } from "./scrolling.js"
+import { DisplayUpdate, maybeClipScrollbars, postUpdateDisplay, setDocumentHeight, updateDisplayIfNeeded } from "./update_display.js"
+import { updateHeightsInViewport } from "./update_lines.js"
+
+// Operations are used to wrap a series of changes to the editor
+// state in such a way that each change won't have to update the
+// cursor and display (which would be awkward, slow, and
+// error-prone). Instead, display updates are batched and then all
+// combined and executed at once.
+
+let nextOpId = 0
+// Start a new operation.
+export function startOperation(cm) {
+  cm.curOp = {
+    cm: cm,
+    viewChanged: false,      // Flag that indicates that lines might need to be redrawn
+    startHeight: cm.doc.height, // Used to detect need to update scrollbar
+    forceUpdate: false,      // Used to force a redraw
+    updateInput: 0,       // Whether to reset the input textarea
+    typing: false,           // Whether this reset should be careful to leave existing text (for compositing)
+    changeObjs: null,        // Accumulated changes, for firing change events
+    cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on
+    cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already
+    selectionChanged: false, // Whether the selection needs to be redrawn
+    updateMaxLine: false,    // Set when the widest line needs to be determined anew
+    scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet
+    scrollToPos: null,       // Used to scroll to a specific position
+    focus: false,
+    id: ++nextOpId,          // Unique ID
+    markArrays: null         // Used by addMarkedSpan
+  }
+  pushOperation(cm.curOp)
+}
+
+// Finish an operation, updating the display and signalling delayed events
+export function endOperation(cm) {
+  let op = cm.curOp
+  if (op) finishOperation(op, group => {
+    for (let i = 0; i < group.ops.length; i++)
+      group.ops[i].cm.curOp = null
+    endOperations(group)
+  })
+}
+
+// The DOM updates done when an operation finishes are batched so
+// that the minimum number of relayouts are required.
+function endOperations(group) {
+  let ops = group.ops
+  for (let i = 0; i < ops.length; i++) // Read DOM
+    endOperation_R1(ops[i])
+  for (let i = 0; i < ops.length; i++) // Write DOM (maybe)
+    endOperation_W1(ops[i])
+  for (let i = 0; i < ops.length; i++) // Read DOM
+    endOperation_R2(ops[i])
+  for (let i = 0; i < ops.length; i++) // Write DOM (maybe)
+    endOperation_W2(ops[i])
+  for (let i = 0; i < ops.length; i++) // Read DOM
+    endOperation_finish(ops[i])
+}
+
+function endOperation_R1(op) {
+  let cm = op.cm, display = cm.display
+  maybeClipScrollbars(cm)
+  if (op.updateMaxLine) findMaxLine(cm)
+
+  op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null ||
+    op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom ||
+                       op.scrollToPos.to.line >= display.viewTo) ||
+    display.maxLineChanged && cm.options.lineWrapping
+  op.update = op.mustUpdate &&
+    new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate)
+}
+
+function endOperation_W1(op) {
+  op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update)
+}
+
+function endOperation_R2(op) {
+  let cm = op.cm, display = cm.display
+  if (op.updatedDisplay) updateHeightsInViewport(cm)
+
+  op.barMeasure = measureForScrollbars(cm)
+
+  // If the max line changed since it was last measured, measure it,
+  // and ensure the document's width matches it.
+  // updateDisplay_W2 will use these properties to do the actual resizing
+  if (display.maxLineChanged && !cm.options.lineWrapping) {
+    op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3
+    cm.display.sizerWidth = op.adjustWidthTo
+    op.barMeasure.scrollWidth =
+      Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth)
+    op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm))
+  }
+
+  if (op.updatedDisplay || op.selectionChanged)
+    op.preparedSelection = display.input.prepareSelection()
+}
+
+function endOperation_W2(op) {
+  let cm = op.cm
+
+  if (op.adjustWidthTo != null) {
+    cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"
+    if (op.maxScrollLeft < cm.doc.scrollLeft)
+      setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true)
+    cm.display.maxLineChanged = false
+  }
+
+  let takeFocus = op.focus && op.focus == activeElt(root(cm))
+  if (op.preparedSelection)
+    cm.display.input.showSelection(op.preparedSelection, takeFocus)
+  if (op.updatedDisplay || op.startHeight != cm.doc.height)
+    updateScrollbars(cm, op.barMeasure)
+  if (op.updatedDisplay)
+    setDocumentHeight(cm, op.barMeasure)
+
+  if (op.selectionChanged) restartBlink(cm)
+
+  if (cm.state.focused && op.updateInput)
+    cm.display.input.reset(op.typing)
+  if (takeFocus) ensureFocus(op.cm)
+}
+
+function endOperation_finish(op) {
+  let cm = op.cm, display = cm.display, doc = cm.doc
+
+  if (op.updatedDisplay) postUpdateDisplay(cm, op.update)
+
+  // Abort mouse wheel delta measurement, when scrolling explicitly
+  if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos))
+    display.wheelStartX = display.wheelStartY = null
+
+  // Propagate the scroll position to the actual DOM scroller
+  if (op.scrollTop != null) setScrollTop(cm, op.scrollTop, op.forceScroll)
+
+  if (op.scrollLeft != null) setScrollLeft(cm, op.scrollLeft, true, true)
+  // If we need to scroll a specific position into view, do so.
+  if (op.scrollToPos) {
+    let rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from),
+                                 clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin)
+    maybeScrollWindow(cm, rect)
+  }
+
+  // Fire events for markers that are hidden/unidden by editing or
+  // undoing
+  let hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers
+  if (hidden) for (let i = 0; i < hidden.length; ++i)
+    if (!hidden[i].lines.length) signal(hidden[i], "hide")
+  if (unhidden) for (let i = 0; i < unhidden.length; ++i)
+    if (unhidden[i].lines.length) signal(unhidden[i], "unhide")
+
+  if (display.wrapper.offsetHeight)
+    doc.scrollTop = cm.display.scroller.scrollTop
+
+  // Fire change events, and delayed event handlers
+  if (op.changeObjs)
+    signal(cm, "changes", cm, op.changeObjs)
+  if (op.update)
+    op.update.finish()
+}
+
+// Run the given function in an operation
+export function runInOp(cm, f) {
+  if (cm.curOp) return f()
+  startOperation(cm)
+  try { return f() }
+  finally { endOperation(cm) }
+}
+// Wraps a function in an operation. Returns the wrapped function.
+export function operation(cm, f) {
+  return function() {
+    if (cm.curOp) return f.apply(cm, arguments)
+    startOperation(cm)
+    try { return f.apply(cm, arguments) }
+    finally { endOperation(cm) }
+  }
+}
+// Used to add methods to editor and doc instances, wrapping them in
+// operations.
+export function methodOp(f) {
+  return function() {
+    if (this.curOp) return f.apply(this, arguments)
+    startOperation(this)
+    try { return f.apply(this, arguments) }
+    finally { endOperation(this) }
+  }
+}
+export function docMethodOp(f) {
+  return function() {
+    let cm = this.cm
+    if (!cm || cm.curOp) return f.apply(this, arguments)
+    startOperation(cm)
+    try { return f.apply(this, arguments) }
+    finally { endOperation(cm) }
+  }
+}