Blame view

js/lib/codemirror/codemirror-5.65.12/src/display/operations.js 7.91 KB
e995f4ad   Erdogan Furkan   Added codeTextAre...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
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, doc } 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(doc(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) }
  }
}