| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- 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"
- }
|