save work

This commit is contained in:
Usman Nasir
2020-10-18 12:29:14 +05:00
parent f7b8dc8aca
commit e092f5afe6
715 changed files with 130433 additions and 53 deletions

View File

@@ -0,0 +1,110 @@
import { gecko, ie, ie_version, mobile, webkit } from "../util/browser.js"
import { elt, eltP } from "../util/dom.js"
import { scrollerGap } from "../util/misc.js"
import { getGutters, renderGutters } from "./gutters.js"
// The display handles the DOM integration, both for input reading
// and content drawing. It holds references to DOM nodes and
// display-related state.
export function Display(place, doc, input, options) {
let d = this
this.input = input
// Covers bottom-right square when both scrollbars are present.
d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler")
d.scrollbarFiller.setAttribute("cm-not-content", "true")
// Covers bottom of gutter when coverGutterNextToScrollbar is on
// and h scrollbar is present.
d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler")
d.gutterFiller.setAttribute("cm-not-content", "true")
// Will contain the actual code, positioned to cover the viewport.
d.lineDiv = eltP("div", null, "CodeMirror-code")
// Elements are added to these to represent selection and cursors.
d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1")
d.cursorDiv = elt("div", null, "CodeMirror-cursors")
// A visibility: hidden element used to find the size of things.
d.measure = elt("div", null, "CodeMirror-measure")
// When lines outside of the viewport are measured, they are drawn in this.
d.lineMeasure = elt("div", null, "CodeMirror-measure")
// Wraps everything that needs to exist inside the vertically-padded coordinate system
d.lineSpace = eltP("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv],
null, "position: relative; outline: none")
let lines = eltP("div", [d.lineSpace], "CodeMirror-lines")
// Moved around its parent to cover visible view.
d.mover = elt("div", [lines], null, "position: relative")
// Set to the height of the document, allowing scrolling.
d.sizer = elt("div", [d.mover], "CodeMirror-sizer")
d.sizerWidth = null
// Behavior of elts with overflow: auto and padding is
// inconsistent across browsers. This is used to ensure the
// scrollable area is big enough.
d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;")
// Will contain the gutters, if any.
d.gutters = elt("div", null, "CodeMirror-gutters")
d.lineGutter = null
// Actual scrollable element.
d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll")
d.scroller.setAttribute("tabIndex", "-1")
// The element in which the editor lives.
d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror")
// Work around IE7 z-index bug (not perfect, hence IE7 not really being supported)
if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0 }
if (!webkit && !(gecko && mobile)) d.scroller.draggable = true
if (place) {
if (place.appendChild) place.appendChild(d.wrapper)
else place(d.wrapper)
}
// Current rendered range (may be bigger than the view window).
d.viewFrom = d.viewTo = doc.first
d.reportedViewFrom = d.reportedViewTo = doc.first
// Information about the rendered lines.
d.view = []
d.renderedView = null
// Holds info about a single rendered line when it was rendered
// for measurement, while not in view.
d.externalMeasured = null
// Empty space (in pixels) above the view
d.viewOffset = 0
d.lastWrapHeight = d.lastWrapWidth = 0
d.updateLineNumbers = null
d.nativeBarWidth = d.barHeight = d.barWidth = 0
d.scrollbarsClipped = false
// Used to only resize the line number gutter when necessary (when
// the amount of lines crosses a boundary that makes its width change)
d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null
// Set to true when a non-horizontal-scrolling line widget is
// added. As an optimization, line widget aligning is skipped when
// this is false.
d.alignWidgets = false
d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null
// Tracks the maximum line length so that the horizontal scrollbar
// can be kept static when scrolling.
d.maxLine = null
d.maxLineLength = 0
d.maxLineChanged = false
// Used for measuring wheel scrolling granularity
d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null
// True when shift is held down.
d.shift = false
// Used to track whether anything happened since the context menu
// was opened.
d.selForContextMenu = null
d.activeTouch = null
d.gutterSpecs = getGutters(options.gutters, options.lineNumbers)
renderGutters(d)
input.init(d)
}

View File

@@ -0,0 +1,47 @@
import { restartBlink } from "./selection.js"
import { webkit } from "../util/browser.js"
import { addClass, rmClass } from "../util/dom.js"
import { signal } from "../util/event.js"
export function ensureFocus(cm) {
if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm) }
}
export function delayBlurEvent(cm) {
cm.state.delayingBlurEvent = true
setTimeout(() => { if (cm.state.delayingBlurEvent) {
cm.state.delayingBlurEvent = false
onBlur(cm)
} }, 100)
}
export function onFocus(cm, e) {
if (cm.state.delayingBlurEvent) cm.state.delayingBlurEvent = false
if (cm.options.readOnly == "nocursor") return
if (!cm.state.focused) {
signal(cm, "focus", cm, e)
cm.state.focused = true
addClass(cm.display.wrapper, "CodeMirror-focused")
// This test prevents this from firing when a context
// menu is closed (since the input reset would kill the
// select-all detection hack)
if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) {
cm.display.input.reset()
if (webkit) setTimeout(() => cm.display.input.reset(true), 20) // Issue #1730
}
cm.display.input.receivedFocus()
}
restartBlink(cm)
}
export function onBlur(cm, e) {
if (cm.state.delayingBlurEvent) return
if (cm.state.focused) {
signal(cm, "blur", cm, e)
cm.state.focused = false
rmClass(cm.display.wrapper, "CodeMirror-focused")
}
clearInterval(cm.display.blinker)
setTimeout(() => { if (!cm.state.focused) cm.display.shift = false }, 150)
}

View File

@@ -0,0 +1,44 @@
import { elt, removeChildren } from "../util/dom.js"
import { regChange } from "./view_tracking.js"
import { alignHorizontally } from "./line_numbers.js"
import { updateGutterSpace } from "./update_display.js"
export function getGutters(gutters, lineNumbers) {
let result = [], sawLineNumbers = false
for (let i = 0; i < gutters.length; i++) {
let name = gutters[i], style = null
if (typeof name != "string") { style = name.style; name = name.className }
if (name == "CodeMirror-linenumbers") {
if (!lineNumbers) continue
else sawLineNumbers = true
}
result.push({className: name, style})
}
if (lineNumbers && !sawLineNumbers) result.push({className: "CodeMirror-linenumbers", style: null})
return result
}
// Rebuild the gutter elements, ensure the margin to the left of the
// code matches their width.
export function renderGutters(display) {
let gutters = display.gutters, specs = display.gutterSpecs
removeChildren(gutters)
display.lineGutter = null
for (let i = 0; i < specs.length; ++i) {
let {className, style} = specs[i]
let gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + className))
if (style) gElt.style.cssText = style
if (className == "CodeMirror-linenumbers") {
display.lineGutter = gElt
gElt.style.width = (display.lineNumWidth || 1) + "px"
}
}
gutters.style.display = specs.length ? "" : "none"
updateGutterSpace(display)
}
export function updateGutters(cm) {
renderGutters(cm.display)
regChange(cm)
alignHorizontally(cm)
}

View File

@@ -0,0 +1,55 @@
import { getContextBefore, highlightLine, processLine } from "../line/highlight.js"
import { copyState } from "../modes.js"
import { bind } from "../util/misc.js"
import { runInOp } from "./operations.js"
import { regLineChange } from "./view_tracking.js"
// HIGHLIGHT WORKER
export function startWorker(cm, time) {
if (cm.doc.highlightFrontier < cm.display.viewTo)
cm.state.highlight.set(time, bind(highlightWorker, cm))
}
function highlightWorker(cm) {
let doc = cm.doc
if (doc.highlightFrontier >= cm.display.viewTo) return
let end = +new Date + cm.options.workTime
let context = getContextBefore(cm, doc.highlightFrontier)
let changedLines = []
doc.iter(context.line, Math.min(doc.first + doc.size, cm.display.viewTo + 500), line => {
if (context.line >= cm.display.viewFrom) { // Visible
let oldStyles = line.styles
let resetState = line.text.length > cm.options.maxHighlightLength ? copyState(doc.mode, context.state) : null
let highlighted = highlightLine(cm, line, context, true)
if (resetState) context.state = resetState
line.styles = highlighted.styles
let oldCls = line.styleClasses, newCls = highlighted.classes
if (newCls) line.styleClasses = newCls
else if (oldCls) line.styleClasses = null
let ischange = !oldStyles || oldStyles.length != line.styles.length ||
oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass)
for (let i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i]
if (ischange) changedLines.push(context.line)
line.stateAfter = context.save()
context.nextLine()
} else {
if (line.text.length <= cm.options.maxHighlightLength)
processLine(cm, line.text, context)
line.stateAfter = context.line % 5 == 0 ? context.save() : null
context.nextLine()
}
if (+new Date > end) {
startWorker(cm, cm.options.workDelay)
return true
}
})
doc.highlightFrontier = context.line
doc.modeFrontier = Math.max(doc.modeFrontier, context.line)
if (changedLines.length) runInOp(cm, () => {
for (let i = 0; i < changedLines.length; i++)
regLineChange(cm, changedLines[i], "text")
})
}

View File

@@ -0,0 +1,48 @@
import { lineNumberFor } from "../line/utils_line.js"
import { compensateForHScroll } from "../measurement/position_measurement.js"
import { elt } from "../util/dom.js"
import { updateGutterSpace } from "./update_display.js"
// Re-align line numbers and gutter marks to compensate for
// horizontal scrolling.
export function alignHorizontally(cm) {
let display = cm.display, view = display.view
if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return
let comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft
let gutterW = display.gutters.offsetWidth, left = comp + "px"
for (let i = 0; i < view.length; i++) if (!view[i].hidden) {
if (cm.options.fixedGutter) {
if (view[i].gutter)
view[i].gutter.style.left = left
if (view[i].gutterBackground)
view[i].gutterBackground.style.left = left
}
let align = view[i].alignable
if (align) for (let j = 0; j < align.length; j++)
align[j].style.left = left
}
if (cm.options.fixedGutter)
display.gutters.style.left = (comp + gutterW) + "px"
}
// Used to ensure that the line number gutter is still the right
// size for the current document size. Returns true when an update
// is needed.
export function maybeUpdateLineNumberWidth(cm) {
if (!cm.options.lineNumbers) return false
let doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display
if (last.length != display.lineNumChars) {
let test = display.measure.appendChild(elt("div", [elt("div", last)],
"CodeMirror-linenumber CodeMirror-gutter-elt"))
let innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW
display.lineGutter.style.width = ""
display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1
display.lineNumWidth = display.lineNumInnerWidth + padding
display.lineNumChars = display.lineNumInnerWidth ? last.length : -1
display.lineGutter.style.width = display.lineNumWidth + "px"
updateGutterSpace(cm.display)
return true
}
return false
}

View File

@@ -0,0 +1,22 @@
import { getMode } from "../modes.js"
import { startWorker } from "./highlight_worker.js"
import { regChange } from "./view_tracking.js"
// Used to get the editor into a consistent state again when options change.
export function loadMode(cm) {
cm.doc.mode = getMode(cm.options, cm.doc.modeOption)
resetModeState(cm)
}
export function resetModeState(cm) {
cm.doc.iter(line => {
if (line.stateAfter) line.stateAfter = null
if (line.styles) line.styles = null
})
cm.doc.modeFrontier = cm.doc.highlightFrontier = cm.doc.first
startWorker(cm, 100)
cm.state.modeGen++
if (cm.curOp) regChange(cm)
}

View File

@@ -0,0 +1,205 @@
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 } 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
}
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()
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) }
}
}

View File

@@ -0,0 +1,115 @@
import { chrome, gecko, ie, mac, presto, safari, webkit } from "../util/browser.js"
import { e_preventDefault } from "../util/event.js"
import { updateDisplaySimple } from "./update_display.js"
import { setScrollLeft, updateScrollTop } from "./scrolling.js"
// Since the delta values reported on mouse wheel events are
// unstandardized between browsers and even browser versions, and
// generally horribly unpredictable, this code starts by measuring
// the scroll effect that the first few mouse wheel events have,
// and, from that, detects the way it can convert deltas to pixel
// offsets afterwards.
//
// The reason we want to know the amount a wheel event will scroll
// is that it gives us a chance to update the display before the
// actual scrolling happens, reducing flickering.
let wheelSamples = 0, wheelPixelsPerUnit = null
// Fill in a browser-detected starting value on browsers where we
// know one. These don't have to be accurate -- the result of them
// being wrong would just be a slight flicker on the first wheel
// scroll (if it is large enough).
if (ie) wheelPixelsPerUnit = -.53
else if (gecko) wheelPixelsPerUnit = 15
else if (chrome) wheelPixelsPerUnit = -.7
else if (safari) wheelPixelsPerUnit = -1/3
function wheelEventDelta(e) {
let dx = e.wheelDeltaX, dy = e.wheelDeltaY
if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail
if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail
else if (dy == null) dy = e.wheelDelta
return {x: dx, y: dy}
}
export function wheelEventPixels(e) {
let delta = wheelEventDelta(e)
delta.x *= wheelPixelsPerUnit
delta.y *= wheelPixelsPerUnit
return delta
}
export function onScrollWheel(cm, e) {
let delta = wheelEventDelta(e), dx = delta.x, dy = delta.y
let display = cm.display, scroll = display.scroller
// Quit if there's nothing to scroll here
let canScrollX = scroll.scrollWidth > scroll.clientWidth
let canScrollY = scroll.scrollHeight > scroll.clientHeight
if (!(dx && canScrollX || dy && canScrollY)) return
// Webkit browsers on OS X abort momentum scrolls when the target
// of the scroll event is removed from the scrollable element.
// This hack (see related code in patchDisplay) makes sure the
// element is kept around.
if (dy && mac && webkit) {
outer: for (let cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) {
for (let i = 0; i < view.length; i++) {
if (view[i].node == cur) {
cm.display.currentWheelTarget = cur
break outer
}
}
}
}
// On some browsers, horizontal scrolling will cause redraws to
// happen before the gutter has been realigned, causing it to
// wriggle around in a most unseemly way. When we have an
// estimated pixels/delta value, we just handle horizontal
// scrolling entirely here. It'll be slightly off from native, but
// better than glitching out.
if (dx && !gecko && !presto && wheelPixelsPerUnit != null) {
if (dy && canScrollY)
updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * wheelPixelsPerUnit))
setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * wheelPixelsPerUnit))
// Only prevent default scrolling if vertical scrolling is
// actually possible. Otherwise, it causes vertical scroll
// jitter on OSX trackpads when deltaX is small and deltaY
// is large (issue #3579)
if (!dy || (dy && canScrollY))
e_preventDefault(e)
display.wheelStartX = null // Abort measurement, if in progress
return
}
// 'Project' the visible viewport to cover the area that is being
// scrolled into view (if we know enough to estimate it).
if (dy && wheelPixelsPerUnit != null) {
let pixels = dy * wheelPixelsPerUnit
let top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight
if (pixels < 0) top = Math.max(0, top + pixels - 50)
else bot = Math.min(cm.doc.height, bot + pixels + 50)
updateDisplaySimple(cm, {top: top, bottom: bot})
}
if (wheelSamples < 20) {
if (display.wheelStartX == null) {
display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop
display.wheelDX = dx; display.wheelDY = dy
setTimeout(() => {
if (display.wheelStartX == null) return
let movedX = scroll.scrollLeft - display.wheelStartX
let movedY = scroll.scrollTop - display.wheelStartY
let sample = (movedY && display.wheelDY && movedY / display.wheelDY) ||
(movedX && display.wheelDX && movedX / display.wheelDX)
display.wheelStartX = display.wheelStartY = null
if (!sample) return
wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1)
++wheelSamples
}, 200)
} else {
display.wheelDX += dx; display.wheelDY += dy
}
}
}

View File

@@ -0,0 +1,193 @@
import { addClass, elt, rmClass } from "../util/dom.js"
import { on } from "../util/event.js"
import { scrollGap, paddingVert } from "../measurement/position_measurement.js"
import { ie, ie_version, mac, mac_geMountainLion } from "../util/browser.js"
import { updateHeightsInViewport } from "./update_lines.js"
import { Delayed } from "../util/misc.js"
import { setScrollLeft, updateScrollTop } from "./scrolling.js"
// SCROLLBARS
// Prepare DOM reads needed to update the scrollbars. Done in one
// shot to minimize update/measure roundtrips.
export function measureForScrollbars(cm) {
let d = cm.display, gutterW = d.gutters.offsetWidth
let docH = Math.round(cm.doc.height + paddingVert(cm.display))
return {
clientHeight: d.scroller.clientHeight,
viewHeight: d.wrapper.clientHeight,
scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth,
viewWidth: d.wrapper.clientWidth,
barLeft: cm.options.fixedGutter ? gutterW : 0,
docHeight: docH,
scrollHeight: docH + scrollGap(cm) + d.barHeight,
nativeBarWidth: d.nativeBarWidth,
gutterWidth: gutterW
}
}
class NativeScrollbars {
constructor(place, scroll, cm) {
this.cm = cm
let vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar")
let horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar")
vert.tabIndex = horiz.tabIndex = -1
place(vert); place(horiz)
on(vert, "scroll", () => {
if (vert.clientHeight) scroll(vert.scrollTop, "vertical")
})
on(horiz, "scroll", () => {
if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal")
})
this.checkedZeroWidth = false
// Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
if (ie && ie_version < 8) this.horiz.style.minHeight = this.vert.style.minWidth = "18px"
}
update(measure) {
let needsH = measure.scrollWidth > measure.clientWidth + 1
let needsV = measure.scrollHeight > measure.clientHeight + 1
let sWidth = measure.nativeBarWidth
if (needsV) {
this.vert.style.display = "block"
this.vert.style.bottom = needsH ? sWidth + "px" : "0"
let totalHeight = measure.viewHeight - (needsH ? sWidth : 0)
// A bug in IE8 can cause this value to be negative, so guard it.
this.vert.firstChild.style.height =
Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px"
} else {
this.vert.style.display = ""
this.vert.firstChild.style.height = "0"
}
if (needsH) {
this.horiz.style.display = "block"
this.horiz.style.right = needsV ? sWidth + "px" : "0"
this.horiz.style.left = measure.barLeft + "px"
let totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0)
this.horiz.firstChild.style.width =
Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + "px"
} else {
this.horiz.style.display = ""
this.horiz.firstChild.style.width = "0"
}
if (!this.checkedZeroWidth && measure.clientHeight > 0) {
if (sWidth == 0) this.zeroWidthHack()
this.checkedZeroWidth = true
}
return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}
}
setScrollLeft(pos) {
if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos
if (this.disableHoriz) this.enableZeroWidthBar(this.horiz, this.disableHoriz, "horiz")
}
setScrollTop(pos) {
if (this.vert.scrollTop != pos) this.vert.scrollTop = pos
if (this.disableVert) this.enableZeroWidthBar(this.vert, this.disableVert, "vert")
}
zeroWidthHack() {
let w = mac && !mac_geMountainLion ? "12px" : "18px"
this.horiz.style.height = this.vert.style.width = w
this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none"
this.disableHoriz = new Delayed
this.disableVert = new Delayed
}
enableZeroWidthBar(bar, delay, type) {
bar.style.pointerEvents = "auto"
function maybeDisable() {
// To find out whether the scrollbar is still visible, we
// check whether the element under the pixel in the bottom
// right corner of the scrollbar box is the scrollbar box
// itself (when the bar is still visible) or its filler child
// (when the bar is hidden). If it is still visible, we keep
// it enabled, if it's hidden, we disable pointer events.
let box = bar.getBoundingClientRect()
let elt = type == "vert" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2)
: document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1)
if (elt != bar) bar.style.pointerEvents = "none"
else delay.set(1000, maybeDisable)
}
delay.set(1000, maybeDisable)
}
clear() {
let parent = this.horiz.parentNode
parent.removeChild(this.horiz)
parent.removeChild(this.vert)
}
}
class NullScrollbars {
update() { return {bottom: 0, right: 0} }
setScrollLeft() {}
setScrollTop() {}
clear() {}
}
export function updateScrollbars(cm, measure) {
if (!measure) measure = measureForScrollbars(cm)
let startWidth = cm.display.barWidth, startHeight = cm.display.barHeight
updateScrollbarsInner(cm, measure)
for (let i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) {
if (startWidth != cm.display.barWidth && cm.options.lineWrapping)
updateHeightsInViewport(cm)
updateScrollbarsInner(cm, measureForScrollbars(cm))
startWidth = cm.display.barWidth; startHeight = cm.display.barHeight
}
}
// Re-synchronize the fake scrollbars with the actual size of the
// content.
function updateScrollbarsInner(cm, measure) {
let d = cm.display
let sizes = d.scrollbars.update(measure)
d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"
d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"
d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent"
if (sizes.right && sizes.bottom) {
d.scrollbarFiller.style.display = "block"
d.scrollbarFiller.style.height = sizes.bottom + "px"
d.scrollbarFiller.style.width = sizes.right + "px"
} else d.scrollbarFiller.style.display = ""
if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {
d.gutterFiller.style.display = "block"
d.gutterFiller.style.height = sizes.bottom + "px"
d.gutterFiller.style.width = measure.gutterWidth + "px"
} else d.gutterFiller.style.display = ""
}
export let scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars}
export function initScrollbars(cm) {
if (cm.display.scrollbars) {
cm.display.scrollbars.clear()
if (cm.display.scrollbars.addClass)
rmClass(cm.display.wrapper, cm.display.scrollbars.addClass)
}
cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](node => {
cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller)
// Prevent clicks in the scrollbars from killing focus
on(node, "mousedown", () => {
if (cm.state.focused) setTimeout(() => cm.display.input.focus(), 0)
})
node.setAttribute("cm-not-content", "true")
}, (pos, axis) => {
if (axis == "horizontal") setScrollLeft(cm, pos)
else updateScrollTop(cm, pos)
}, cm)
if (cm.display.scrollbars.addClass)
addClass(cm.display.wrapper, cm.display.scrollbars.addClass)
}

View File

@@ -0,0 +1,185 @@
import { Pos } from "../line/pos.js"
import { cursorCoords, displayHeight, displayWidth, estimateCoords, paddingTop, paddingVert, scrollGap, textHeight } from "../measurement/position_measurement.js"
import { gecko, phantom } from "../util/browser.js"
import { elt } from "../util/dom.js"
import { signalDOMEvent } from "../util/event.js"
import { startWorker } from "./highlight_worker.js"
import { alignHorizontally } from "./line_numbers.js"
import { updateDisplaySimple } from "./update_display.js"
// SCROLLING THINGS INTO VIEW
// If an editor sits on the top or bottom of the window, partially
// scrolled out of view, this ensures that the cursor is visible.
export function maybeScrollWindow(cm, rect) {
if (signalDOMEvent(cm, "scrollCursorIntoView")) return
let display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null
if (rect.top + box.top < 0) doScroll = true
else if (rect.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false
if (doScroll != null && !phantom) {
let scrollNode = elt("div", "\u200b", null, `position: absolute;
top: ${rect.top - display.viewOffset - paddingTop(cm.display)}px;
height: ${rect.bottom - rect.top + scrollGap(cm) + display.barHeight}px;
left: ${rect.left}px; width: ${Math.max(2, rect.right - rect.left)}px;`)
cm.display.lineSpace.appendChild(scrollNode)
scrollNode.scrollIntoView(doScroll)
cm.display.lineSpace.removeChild(scrollNode)
}
}
// Scroll a given position into view (immediately), verifying that
// it actually became visible (as line heights are accurately
// measured, the position of something may 'drift' during drawing).
export function scrollPosIntoView(cm, pos, end, margin) {
if (margin == null) margin = 0
let rect
if (!cm.options.lineWrapping && pos == end) {
// Set pos and end to the cursor positions around the character pos sticks to
// If pos.sticky == "before", that is around pos.ch - 1, otherwise around pos.ch
// If pos == Pos(_, 0, "before"), pos and end are unchanged
pos = pos.ch ? Pos(pos.line, pos.sticky == "before" ? pos.ch - 1 : pos.ch, "after") : pos
end = pos.sticky == "before" ? Pos(pos.line, pos.ch + 1, "before") : pos
}
for (let limit = 0; limit < 5; limit++) {
let changed = false
let coords = cursorCoords(cm, pos)
let endCoords = !end || end == pos ? coords : cursorCoords(cm, end)
rect = {left: Math.min(coords.left, endCoords.left),
top: Math.min(coords.top, endCoords.top) - margin,
right: Math.max(coords.left, endCoords.left),
bottom: Math.max(coords.bottom, endCoords.bottom) + margin}
let scrollPos = calculateScrollPos(cm, rect)
let startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft
if (scrollPos.scrollTop != null) {
updateScrollTop(cm, scrollPos.scrollTop)
if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true
}
if (scrollPos.scrollLeft != null) {
setScrollLeft(cm, scrollPos.scrollLeft)
if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true
}
if (!changed) break
}
return rect
}
// Scroll a given set of coordinates into view (immediately).
export function scrollIntoView(cm, rect) {
let scrollPos = calculateScrollPos(cm, rect)
if (scrollPos.scrollTop != null) updateScrollTop(cm, scrollPos.scrollTop)
if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft)
}
// Calculate a new scroll position needed to scroll the given
// rectangle into view. Returns an object with scrollTop and
// scrollLeft properties. When these are undefined, the
// vertical/horizontal position does not need to be adjusted.
function calculateScrollPos(cm, rect) {
let display = cm.display, snapMargin = textHeight(cm.display)
if (rect.top < 0) rect.top = 0
let screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop
let screen = displayHeight(cm), result = {}
if (rect.bottom - rect.top > screen) rect.bottom = rect.top + screen
let docBottom = cm.doc.height + paddingVert(display)
let atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMargin
if (rect.top < screentop) {
result.scrollTop = atTop ? 0 : rect.top
} else if (rect.bottom > screentop + screen) {
let newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - screen)
if (newTop != screentop) result.scrollTop = newTop
}
let gutterSpace = cm.options.fixedGutter ? 0 : display.gutters.offsetWidth
let screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft - gutterSpace
let screenw = displayWidth(cm) - display.gutters.offsetWidth
let tooWide = rect.right - rect.left > screenw
if (tooWide) rect.right = rect.left + screenw
if (rect.left < 10)
result.scrollLeft = 0
else if (rect.left < screenleft)
result.scrollLeft = Math.max(0, rect.left + gutterSpace - (tooWide ? 0 : 10))
else if (rect.right > screenw + screenleft - 3)
result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw
return result
}
// Store a relative adjustment to the scroll position in the current
// operation (to be applied when the operation finishes).
export function addToScrollTop(cm, top) {
if (top == null) return
resolveScrollToPos(cm)
cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top
}
// Make sure that at the end of the operation the current cursor is
// shown.
export function ensureCursorVisible(cm) {
resolveScrollToPos(cm)
let cur = cm.getCursor()
cm.curOp.scrollToPos = {from: cur, to: cur, margin: cm.options.cursorScrollMargin}
}
export function scrollToCoords(cm, x, y) {
if (x != null || y != null) resolveScrollToPos(cm)
if (x != null) cm.curOp.scrollLeft = x
if (y != null) cm.curOp.scrollTop = y
}
export function scrollToRange(cm, range) {
resolveScrollToPos(cm)
cm.curOp.scrollToPos = range
}
// When an operation has its scrollToPos property set, and another
// scroll action is applied before the end of the operation, this
// 'simulates' scrolling that position into view in a cheap way, so
// that the effect of intermediate scroll commands is not ignored.
function resolveScrollToPos(cm) {
let range = cm.curOp.scrollToPos
if (range) {
cm.curOp.scrollToPos = null
let from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to)
scrollToCoordsRange(cm, from, to, range.margin)
}
}
export function scrollToCoordsRange(cm, from, to, margin) {
let sPos = calculateScrollPos(cm, {
left: Math.min(from.left, to.left),
top: Math.min(from.top, to.top) - margin,
right: Math.max(from.right, to.right),
bottom: Math.max(from.bottom, to.bottom) + margin
})
scrollToCoords(cm, sPos.scrollLeft, sPos.scrollTop)
}
// Sync the scrollable area and scrollbars, ensure the viewport
// covers the visible area.
export function updateScrollTop(cm, val) {
if (Math.abs(cm.doc.scrollTop - val) < 2) return
if (!gecko) updateDisplaySimple(cm, {top: val})
setScrollTop(cm, val, true)
if (gecko) updateDisplaySimple(cm)
startWorker(cm, 100)
}
export function setScrollTop(cm, val, forceScroll) {
val = Math.max(0, Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, val))
if (cm.display.scroller.scrollTop == val && !forceScroll) return
cm.doc.scrollTop = val
cm.display.scrollbars.setScrollTop(val)
if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val
}
// Sync scroller and scrollbar, ensure the gutter elements are
// aligned.
export function setScrollLeft(cm, val, isScroller, forceScroll) {
val = Math.max(0, Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth))
if ((isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) && !forceScroll) return
cm.doc.scrollLeft = val
alignHorizontally(cm)
if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val
cm.display.scrollbars.setScrollLeft(val)
}

View File

@@ -0,0 +1,161 @@
import { Pos } from "../line/pos.js"
import { visualLine } from "../line/spans.js"
import { getLine } from "../line/utils_line.js"
import { charCoords, cursorCoords, displayWidth, paddingH, wrappedLineExtentChar } from "../measurement/position_measurement.js"
import { getOrder, iterateBidiSections } from "../util/bidi.js"
import { elt } from "../util/dom.js"
import { onBlur } from "./focus.js"
export function updateSelection(cm) {
cm.display.input.showSelection(cm.display.input.prepareSelection())
}
export function prepareSelection(cm, primary = true) {
let doc = cm.doc, result = {}
let curFragment = result.cursors = document.createDocumentFragment()
let selFragment = result.selection = document.createDocumentFragment()
for (let i = 0; i < doc.sel.ranges.length; i++) {
if (!primary && i == doc.sel.primIndex) continue
let range = doc.sel.ranges[i]
if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) continue
let collapsed = range.empty()
if (collapsed || cm.options.showCursorWhenSelecting)
drawSelectionCursor(cm, range.head, curFragment)
if (!collapsed)
drawSelectionRange(cm, range, selFragment)
}
return result
}
// Draws a cursor for the given range
export function drawSelectionCursor(cm, head, output) {
let pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine)
let cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor"))
cursor.style.left = pos.left + "px"
cursor.style.top = pos.top + "px"
cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"
if (pos.other) {
// Secondary cursor, shown when on a 'jump' in bi-directional text
let otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"))
otherCursor.style.display = ""
otherCursor.style.left = pos.other.left + "px"
otherCursor.style.top = pos.other.top + "px"
otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"
}
}
function cmpCoords(a, b) { return a.top - b.top || a.left - b.left }
// Draws the given range as a highlighted selection
function drawSelectionRange(cm, range, output) {
let display = cm.display, doc = cm.doc
let fragment = document.createDocumentFragment()
let padding = paddingH(cm.display), leftSide = padding.left
let rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right
let docLTR = doc.direction == "ltr"
function add(left, top, width, bottom) {
if (top < 0) top = 0
top = Math.round(top)
bottom = Math.round(bottom)
fragment.appendChild(elt("div", null, "CodeMirror-selected", `position: absolute; left: ${left}px;
top: ${top}px; width: ${width == null ? rightSide - left : width}px;
height: ${bottom - top}px`))
}
function drawForLine(line, fromArg, toArg) {
let lineObj = getLine(doc, line)
let lineLen = lineObj.text.length
let start, end
function coords(ch, bias) {
return charCoords(cm, Pos(line, ch), "div", lineObj, bias)
}
function wrapX(pos, dir, side) {
let extent = wrappedLineExtentChar(cm, lineObj, null, pos)
let prop = (dir == "ltr") == (side == "after") ? "left" : "right"
let ch = side == "after" ? extent.begin : extent.end - (/\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1)
return coords(ch, prop)[prop]
}
let order = getOrder(lineObj, doc.direction)
iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, (from, to, dir, i) => {
let ltr = dir == "ltr"
let fromPos = coords(from, ltr ? "left" : "right")
let toPos = coords(to - 1, ltr ? "right" : "left")
let openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen
let first = i == 0, last = !order || i == order.length - 1
if (toPos.top - fromPos.top <= 3) { // Single line
let openLeft = (docLTR ? openStart : openEnd) && first
let openRight = (docLTR ? openEnd : openStart) && last
let left = openLeft ? leftSide : (ltr ? fromPos : toPos).left
let right = openRight ? rightSide : (ltr ? toPos : fromPos).right
add(left, fromPos.top, right - left, fromPos.bottom)
} else { // Multiple lines
let topLeft, topRight, botLeft, botRight
if (ltr) {
topLeft = docLTR && openStart && first ? leftSide : fromPos.left
topRight = docLTR ? rightSide : wrapX(from, dir, "before")
botLeft = docLTR ? leftSide : wrapX(to, dir, "after")
botRight = docLTR && openEnd && last ? rightSide : toPos.right
} else {
topLeft = !docLTR ? leftSide : wrapX(from, dir, "before")
topRight = !docLTR && openStart && first ? rightSide : fromPos.right
botLeft = !docLTR && openEnd && last ? leftSide : toPos.left
botRight = !docLTR ? rightSide : wrapX(to, dir, "after")
}
add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom)
if (fromPos.bottom < toPos.top) add(leftSide, fromPos.bottom, null, toPos.top)
add(botLeft, toPos.top, botRight - botLeft, toPos.bottom)
}
if (!start || cmpCoords(fromPos, start) < 0) start = fromPos
if (cmpCoords(toPos, start) < 0) start = toPos
if (!end || cmpCoords(fromPos, end) < 0) end = fromPos
if (cmpCoords(toPos, end) < 0) end = toPos
})
return {start: start, end: end}
}
let sFrom = range.from(), sTo = range.to()
if (sFrom.line == sTo.line) {
drawForLine(sFrom.line, sFrom.ch, sTo.ch)
} else {
let fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line)
let singleVLine = visualLine(fromLine) == visualLine(toLine)
let leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end
let rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start
if (singleVLine) {
if (leftEnd.top < rightStart.top - 2) {
add(leftEnd.right, leftEnd.top, null, leftEnd.bottom)
add(leftSide, rightStart.top, rightStart.left, rightStart.bottom)
} else {
add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom)
}
}
if (leftEnd.bottom < rightStart.top)
add(leftSide, leftEnd.bottom, null, rightStart.top)
}
output.appendChild(fragment)
}
// Cursor-blinking
export function restartBlink(cm) {
if (!cm.state.focused) return
let display = cm.display
clearInterval(display.blinker)
let on = true
display.cursorDiv.style.visibility = ""
if (cm.options.cursorBlinkRate > 0)
display.blinker = setInterval(() => {
if (!cm.hasFocus()) onBlur(cm)
display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"
}, cm.options.cursorBlinkRate)
else if (cm.options.cursorBlinkRate < 0)
display.cursorDiv.style.visibility = "hidden"
}

View File

@@ -0,0 +1,263 @@
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 } from "../util/dom.js"
import { hasHandler, signal } from "../util/event.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()
if (!active || !contains(cm.display.lineDiv, active)) return null
let result = {activeElt: active}
if (window.getSelection) {
let sel = window.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()) return
snapshot.activeElt.focus()
if (!/^(INPUT|TEXTAREA)$/.test(snapshot.activeElt.nodeName) &&
snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) {
let sel = window.getSelection(), range = document.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"
}
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"
}

View File

@@ -0,0 +1,188 @@
import { buildLineContent } from "../line/line_data.js"
import { lineNumberFor } from "../line/utils_line.js"
import { ie, ie_version } from "../util/browser.js"
import { elt, classTest } from "../util/dom.js"
import { signalLater } from "../util/operation_group.js"
// When an aspect of a line changes, a string is added to
// lineView.changes. This updates the relevant part of the line's
// DOM structure.
export function updateLineForChanges(cm, lineView, lineN, dims) {
for (let j = 0; j < lineView.changes.length; j++) {
let type = lineView.changes[j]
if (type == "text") updateLineText(cm, lineView)
else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims)
else if (type == "class") updateLineClasses(cm, lineView)
else if (type == "widget") updateLineWidgets(cm, lineView, dims)
}
lineView.changes = null
}
// Lines with gutter elements, widgets or a background class need to
// be wrapped, and have the extra elements added to the wrapper div
function ensureLineWrapped(lineView) {
if (lineView.node == lineView.text) {
lineView.node = elt("div", null, null, "position: relative")
if (lineView.text.parentNode)
lineView.text.parentNode.replaceChild(lineView.node, lineView.text)
lineView.node.appendChild(lineView.text)
if (ie && ie_version < 8) lineView.node.style.zIndex = 2
}
return lineView.node
}
function updateLineBackground(cm, lineView) {
let cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass
if (cls) cls += " CodeMirror-linebackground"
if (lineView.background) {
if (cls) lineView.background.className = cls
else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null }
} else if (cls) {
let wrap = ensureLineWrapped(lineView)
lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild)
cm.display.input.setUneditable(lineView.background)
}
}
// Wrapper around buildLineContent which will reuse the structure
// in display.externalMeasured when possible.
function getLineContent(cm, lineView) {
let ext = cm.display.externalMeasured
if (ext && ext.line == lineView.line) {
cm.display.externalMeasured = null
lineView.measure = ext.measure
return ext.built
}
return buildLineContent(cm, lineView)
}
// Redraw the line's text. Interacts with the background and text
// classes because the mode may output tokens that influence these
// classes.
function updateLineText(cm, lineView) {
let cls = lineView.text.className
let built = getLineContent(cm, lineView)
if (lineView.text == lineView.node) lineView.node = built.pre
lineView.text.parentNode.replaceChild(built.pre, lineView.text)
lineView.text = built.pre
if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) {
lineView.bgClass = built.bgClass
lineView.textClass = built.textClass
updateLineClasses(cm, lineView)
} else if (cls) {
lineView.text.className = cls
}
}
function updateLineClasses(cm, lineView) {
updateLineBackground(cm, lineView)
if (lineView.line.wrapClass)
ensureLineWrapped(lineView).className = lineView.line.wrapClass
else if (lineView.node != lineView.text)
lineView.node.className = ""
let textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass
lineView.text.className = textClass || ""
}
function updateLineGutter(cm, lineView, lineN, dims) {
if (lineView.gutter) {
lineView.node.removeChild(lineView.gutter)
lineView.gutter = null
}
if (lineView.gutterBackground) {
lineView.node.removeChild(lineView.gutterBackground)
lineView.gutterBackground = null
}
if (lineView.line.gutterClass) {
let wrap = ensureLineWrapped(lineView)
lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass,
`left: ${cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth}px; width: ${dims.gutterTotalWidth}px`)
cm.display.input.setUneditable(lineView.gutterBackground)
wrap.insertBefore(lineView.gutterBackground, lineView.text)
}
let markers = lineView.line.gutterMarkers
if (cm.options.lineNumbers || markers) {
let wrap = ensureLineWrapped(lineView)
let gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", `left: ${cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth}px`)
cm.display.input.setUneditable(gutterWrap)
wrap.insertBefore(gutterWrap, lineView.text)
if (lineView.line.gutterClass)
gutterWrap.className += " " + lineView.line.gutterClass
if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
lineView.lineNumber = gutterWrap.appendChild(
elt("div", lineNumberFor(cm.options, lineN),
"CodeMirror-linenumber CodeMirror-gutter-elt",
`left: ${dims.gutterLeft["CodeMirror-linenumbers"]}px; width: ${cm.display.lineNumInnerWidth}px`))
if (markers) for (let k = 0; k < cm.display.gutterSpecs.length; ++k) {
let id = cm.display.gutterSpecs[k].className, found = markers.hasOwnProperty(id) && markers[id]
if (found)
gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt",
`left: ${dims.gutterLeft[id]}px; width: ${dims.gutterWidth[id]}px`))
}
}
}
function updateLineWidgets(cm, lineView, dims) {
if (lineView.alignable) lineView.alignable = null
let isWidget = classTest("CodeMirror-linewidget")
for (let node = lineView.node.firstChild, next; node; node = next) {
next = node.nextSibling
if (isWidget.test(node.className)) lineView.node.removeChild(node)
}
insertLineWidgets(cm, lineView, dims)
}
// Build a line's DOM representation from scratch
export function buildLineElement(cm, lineView, lineN, dims) {
let built = getLineContent(cm, lineView)
lineView.text = lineView.node = built.pre
if (built.bgClass) lineView.bgClass = built.bgClass
if (built.textClass) lineView.textClass = built.textClass
updateLineClasses(cm, lineView)
updateLineGutter(cm, lineView, lineN, dims)
insertLineWidgets(cm, lineView, dims)
return lineView.node
}
// A lineView may contain multiple logical lines (when merged by
// collapsed spans). The widgets for all of them need to be drawn.
function insertLineWidgets(cm, lineView, dims) {
insertLineWidgetsFor(cm, lineView.line, lineView, dims, true)
if (lineView.rest) for (let i = 0; i < lineView.rest.length; i++)
insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false)
}
function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) {
if (!line.widgets) return
let wrap = ensureLineWrapped(lineView)
for (let i = 0, ws = line.widgets; i < ws.length; ++i) {
let widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget" + (widget.className ? " " + widget.className : ""))
if (!widget.handleMouseEvents) node.setAttribute("cm-ignore-events", "true")
positionLineWidget(widget, node, lineView, dims)
cm.display.input.setUneditable(node)
if (allowAbove && widget.above)
wrap.insertBefore(node, lineView.gutter || lineView.text)
else
wrap.appendChild(node)
signalLater(widget, "redraw")
}
}
function positionLineWidget(widget, node, lineView, dims) {
if (widget.noHScroll) {
;(lineView.alignable || (lineView.alignable = [])).push(node)
let width = dims.wrapperWidth
node.style.left = dims.fixedPos + "px"
if (!widget.coverGutter) {
width -= dims.gutterTotalWidth
node.style.paddingLeft = dims.gutterTotalWidth + "px"
}
node.style.width = width + "px"
}
if (widget.coverGutter) {
node.style.zIndex = 5
node.style.position = "relative"
if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px"
}
}

View File

@@ -0,0 +1,76 @@
import { heightAtLine } from "../line/spans.js"
import { getLine, lineAtHeight, updateLineHeight } from "../line/utils_line.js"
import { paddingTop, charWidth } from "../measurement/position_measurement.js"
import { ie, ie_version } from "../util/browser.js"
// Read the actual heights of the rendered lines, and update their
// stored heights to match.
export function updateHeightsInViewport(cm) {
let display = cm.display
let prevBottom = display.lineDiv.offsetTop
for (let i = 0; i < display.view.length; i++) {
let cur = display.view[i], wrapping = cm.options.lineWrapping
let height, width = 0
if (cur.hidden) continue
if (ie && ie_version < 8) {
let bot = cur.node.offsetTop + cur.node.offsetHeight
height = bot - prevBottom
prevBottom = bot
} else {
let box = cur.node.getBoundingClientRect()
height = box.bottom - box.top
// Check that lines don't extend past the right of the current
// editor width
if (!wrapping && cur.text.firstChild)
width = cur.text.firstChild.getBoundingClientRect().right - box.left - 1
}
let diff = cur.line.height - height
if (diff > .005 || diff < -.005) {
updateLineHeight(cur.line, height)
updateWidgetHeight(cur.line)
if (cur.rest) for (let j = 0; j < cur.rest.length; j++)
updateWidgetHeight(cur.rest[j])
}
if (width > cm.display.sizerWidth) {
let chWidth = Math.ceil(width / charWidth(cm.display))
if (chWidth > cm.display.maxLineLength) {
cm.display.maxLineLength = chWidth
cm.display.maxLine = cur.line
cm.display.maxLineChanged = true
}
}
}
}
// Read and store the height of line widgets associated with the
// given line.
function updateWidgetHeight(line) {
if (line.widgets) for (let i = 0; i < line.widgets.length; ++i) {
let w = line.widgets[i], parent = w.node.parentNode
if (parent) w.height = parent.offsetHeight
}
}
// Compute the lines that are visible in a given viewport (defaults
// the the current scroll position). viewport may contain top,
// height, and ensure (see op.scrollToPos) properties.
export function visibleLines(display, doc, viewport) {
let top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop
top = Math.floor(top - paddingTop(display))
let bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight
let from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom)
// Ensure is a {from: {line, ch}, to: {line, ch}} object, and
// forces those lines into the viewport (if possible).
if (viewport && viewport.ensure) {
let ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line
if (ensureFrom < from) {
from = ensureFrom
to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight)
} else if (Math.min(ensureTo, doc.lastLine()) >= to) {
from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight)
to = ensureTo
}
}
return {from: from, to: Math.max(to, from + 1)}
}

View File

@@ -0,0 +1,153 @@
import { buildViewArray } from "../line/line_data.js"
import { sawCollapsedSpans } from "../line/saw_special_spans.js"
import { visualLineEndNo, visualLineNo } from "../line/spans.js"
import { findViewIndex } from "../measurement/position_measurement.js"
import { indexOf } from "../util/misc.js"
// Updates the display.view data structure for a given change to the
// document. From and to are in pre-change coordinates. Lendiff is
// the amount of lines added or subtracted by the change. This is
// used for changes that span multiple lines, or change the way
// lines are divided into visual lines. regLineChange (below)
// registers single-line changes.
export function regChange(cm, from, to, lendiff) {
if (from == null) from = cm.doc.first
if (to == null) to = cm.doc.first + cm.doc.size
if (!lendiff) lendiff = 0
let display = cm.display
if (lendiff && to < display.viewTo &&
(display.updateLineNumbers == null || display.updateLineNumbers > from))
display.updateLineNumbers = from
cm.curOp.viewChanged = true
if (from >= display.viewTo) { // Change after
if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo)
resetView(cm)
} else if (to <= display.viewFrom) { // Change before
if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) {
resetView(cm)
} else {
display.viewFrom += lendiff
display.viewTo += lendiff
}
} else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap
resetView(cm)
} else if (from <= display.viewFrom) { // Top overlap
let cut = viewCuttingPoint(cm, to, to + lendiff, 1)
if (cut) {
display.view = display.view.slice(cut.index)
display.viewFrom = cut.lineN
display.viewTo += lendiff
} else {
resetView(cm)
}
} else if (to >= display.viewTo) { // Bottom overlap
let cut = viewCuttingPoint(cm, from, from, -1)
if (cut) {
display.view = display.view.slice(0, cut.index)
display.viewTo = cut.lineN
} else {
resetView(cm)
}
} else { // Gap in the middle
let cutTop = viewCuttingPoint(cm, from, from, -1)
let cutBot = viewCuttingPoint(cm, to, to + lendiff, 1)
if (cutTop && cutBot) {
display.view = display.view.slice(0, cutTop.index)
.concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN))
.concat(display.view.slice(cutBot.index))
display.viewTo += lendiff
} else {
resetView(cm)
}
}
let ext = display.externalMeasured
if (ext) {
if (to < ext.lineN)
ext.lineN += lendiff
else if (from < ext.lineN + ext.size)
display.externalMeasured = null
}
}
// Register a change to a single line. Type must be one of "text",
// "gutter", "class", "widget"
export function regLineChange(cm, line, type) {
cm.curOp.viewChanged = true
let display = cm.display, ext = cm.display.externalMeasured
if (ext && line >= ext.lineN && line < ext.lineN + ext.size)
display.externalMeasured = null
if (line < display.viewFrom || line >= display.viewTo) return
let lineView = display.view[findViewIndex(cm, line)]
if (lineView.node == null) return
let arr = lineView.changes || (lineView.changes = [])
if (indexOf(arr, type) == -1) arr.push(type)
}
// Clear the view.
export function resetView(cm) {
cm.display.viewFrom = cm.display.viewTo = cm.doc.first
cm.display.view = []
cm.display.viewOffset = 0
}
function viewCuttingPoint(cm, oldN, newN, dir) {
let index = findViewIndex(cm, oldN), diff, view = cm.display.view
if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size)
return {index: index, lineN: newN}
let n = cm.display.viewFrom
for (let i = 0; i < index; i++)
n += view[i].size
if (n != oldN) {
if (dir > 0) {
if (index == view.length - 1) return null
diff = (n + view[index].size) - oldN
index++
} else {
diff = n - oldN
}
oldN += diff; newN += diff
}
while (visualLineNo(cm.doc, newN) != newN) {
if (index == (dir < 0 ? 0 : view.length - 1)) return null
newN += dir * view[index - (dir < 0 ? 1 : 0)].size
index += dir
}
return {index: index, lineN: newN}
}
// Force the view to cover a given range, adding empty view element
// or clipping off existing ones as needed.
export function adjustView(cm, from, to) {
let display = cm.display, view = display.view
if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) {
display.view = buildViewArray(cm, from, to)
display.viewFrom = from
} else {
if (display.viewFrom > from)
display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view)
else if (display.viewFrom < from)
display.view = display.view.slice(findViewIndex(cm, from))
display.viewFrom = from
if (display.viewTo < to)
display.view = display.view.concat(buildViewArray(cm, display.viewTo, to))
else if (display.viewTo > to)
display.view = display.view.slice(0, findViewIndex(cm, to))
}
display.viewTo = to
}
// Count the number of lines in the view whose DOM representation is
// out of date (or nonexistent).
export function countDirtyView(cm) {
let view = cm.display.view, dirty = 0
for (let i = 0; i < view.length; i++) {
let lineView = view[i]
if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty
}
return dirty
}