openrat-cms

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

update_display.js (10173B)


      1 import { sawCollapsedSpans } from "../line/saw_special_spans.js"
      2 import { heightAtLine, visualLineEndNo, visualLineNo } from "../line/spans.js"
      3 import { getLine, lineNumberFor } from "../line/utils_line.js"
      4 import { displayHeight, displayWidth, getDimensions, paddingVert, scrollGap } from "../measurement/position_measurement.js"
      5 import { mac, webkit } from "../util/browser.js"
      6 import { activeElt, removeChildren, contains } from "../util/dom.js"
      7 import { hasHandler, signal } from "../util/event.js"
      8 import { indexOf } from "../util/misc.js"
      9 
     10 import { buildLineElement, updateLineForChanges } from "./update_line.js"
     11 import { startWorker } from "./highlight_worker.js"
     12 import { maybeUpdateLineNumberWidth } from "./line_numbers.js"
     13 import { measureForScrollbars, updateScrollbars } from "./scrollbars.js"
     14 import { updateSelection } from "./selection.js"
     15 import { updateHeightsInViewport, visibleLines } from "./update_lines.js"
     16 import { adjustView, countDirtyView, resetView } from "./view_tracking.js"
     17 
     18 // DISPLAY DRAWING
     19 
     20 export class DisplayUpdate {
     21   constructor(cm, viewport, force) {
     22     let display = cm.display
     23 
     24     this.viewport = viewport
     25     // Store some values that we'll need later (but don't want to force a relayout for)
     26     this.visible = visibleLines(display, cm.doc, viewport)
     27     this.editorIsHidden = !display.wrapper.offsetWidth
     28     this.wrapperHeight = display.wrapper.clientHeight
     29     this.wrapperWidth = display.wrapper.clientWidth
     30     this.oldDisplayWidth = displayWidth(cm)
     31     this.force = force
     32     this.dims = getDimensions(cm)
     33     this.events = []
     34   }
     35 
     36   signal(emitter, type) {
     37     if (hasHandler(emitter, type))
     38       this.events.push(arguments)
     39   }
     40   finish() {
     41     for (let i = 0; i < this.events.length; i++)
     42       signal.apply(null, this.events[i])
     43   }
     44 }
     45 
     46 export function maybeClipScrollbars(cm) {
     47   let display = cm.display
     48   if (!display.scrollbarsClipped && display.scroller.offsetWidth) {
     49     display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth
     50     display.heightForcer.style.height = scrollGap(cm) + "px"
     51     display.sizer.style.marginBottom = -display.nativeBarWidth + "px"
     52     display.sizer.style.borderRightWidth = scrollGap(cm) + "px"
     53     display.scrollbarsClipped = true
     54   }
     55 }
     56 
     57 function selectionSnapshot(cm) {
     58   if (cm.hasFocus()) return null
     59   let active = activeElt()
     60   if (!active || !contains(cm.display.lineDiv, active)) return null
     61   let result = {activeElt: active}
     62   if (window.getSelection) {
     63     let sel = window.getSelection()
     64     if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) {
     65       result.anchorNode = sel.anchorNode
     66       result.anchorOffset = sel.anchorOffset
     67       result.focusNode = sel.focusNode
     68       result.focusOffset = sel.focusOffset
     69     }
     70   }
     71   return result
     72 }
     73 
     74 function restoreSelection(snapshot) {
     75   if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt()) return
     76   snapshot.activeElt.focus()
     77   if (snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) {
     78     let sel = window.getSelection(), range = document.createRange()
     79     range.setEnd(snapshot.anchorNode, snapshot.anchorOffset)
     80     range.collapse(false)
     81     sel.removeAllRanges()
     82     sel.addRange(range)
     83     sel.extend(snapshot.focusNode, snapshot.focusOffset)
     84   }
     85 }
     86 
     87 // Does the actual updating of the line display. Bails out
     88 // (returning false) when there is nothing to be done and forced is
     89 // false.
     90 export function updateDisplayIfNeeded(cm, update) {
     91   let display = cm.display, doc = cm.doc
     92 
     93   if (update.editorIsHidden) {
     94     resetView(cm)
     95     return false
     96   }
     97 
     98   // Bail out if the visible area is already rendered and nothing changed.
     99   if (!update.force &&
    100       update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo &&
    101       (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) &&
    102       display.renderedView == display.view && countDirtyView(cm) == 0)
    103     return false
    104 
    105   if (maybeUpdateLineNumberWidth(cm)) {
    106     resetView(cm)
    107     update.dims = getDimensions(cm)
    108   }
    109 
    110   // Compute a suitable new viewport (from & to)
    111   let end = doc.first + doc.size
    112   let from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first)
    113   let to = Math.min(end, update.visible.to + cm.options.viewportMargin)
    114   if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom)
    115   if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo)
    116   if (sawCollapsedSpans) {
    117     from = visualLineNo(cm.doc, from)
    118     to = visualLineEndNo(cm.doc, to)
    119   }
    120 
    121   let different = from != display.viewFrom || to != display.viewTo ||
    122     display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth
    123   adjustView(cm, from, to)
    124 
    125   display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom))
    126   // Position the mover div to align with the current scroll position
    127   cm.display.mover.style.top = display.viewOffset + "px"
    128 
    129   let toUpdate = countDirtyView(cm)
    130   if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view &&
    131       (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo))
    132     return false
    133 
    134   // For big changes, we hide the enclosing element during the
    135   // update, since that speeds up the operations on most browsers.
    136   let selSnapshot = selectionSnapshot(cm)
    137   if (toUpdate > 4) display.lineDiv.style.display = "none"
    138   patchDisplay(cm, display.updateLineNumbers, update.dims)
    139   if (toUpdate > 4) display.lineDiv.style.display = ""
    140   display.renderedView = display.view
    141   // There might have been a widget with a focused element that got
    142   // hidden or updated, if so re-focus it.
    143   restoreSelection(selSnapshot)
    144 
    145   // Prevent selection and cursors from interfering with the scroll
    146   // width and height.
    147   removeChildren(display.cursorDiv)
    148   removeChildren(display.selectionDiv)
    149   display.gutters.style.height = display.sizer.style.minHeight = 0
    150 
    151   if (different) {
    152     display.lastWrapHeight = update.wrapperHeight
    153     display.lastWrapWidth = update.wrapperWidth
    154     startWorker(cm, 400)
    155   }
    156 
    157   display.updateLineNumbers = null
    158 
    159   return true
    160 }
    161 
    162 export function postUpdateDisplay(cm, update) {
    163   let viewport = update.viewport
    164 
    165   for (let first = true;; first = false) {
    166     if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) {
    167       // Clip forced viewport to actual scrollable area.
    168       if (viewport && viewport.top != null)
    169         viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}
    170       // Updated line heights might result in the drawn area not
    171       // actually covering the viewport. Keep looping until it does.
    172       update.visible = visibleLines(cm.display, cm.doc, viewport)
    173       if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo)
    174         break
    175     }
    176     if (!updateDisplayIfNeeded(cm, update)) break
    177     updateHeightsInViewport(cm)
    178     let barMeasure = measureForScrollbars(cm)
    179     updateSelection(cm)
    180     updateScrollbars(cm, barMeasure)
    181     setDocumentHeight(cm, barMeasure)
    182     update.force = false
    183   }
    184 
    185   update.signal(cm, "update", cm)
    186   if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) {
    187     update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo)
    188     cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo
    189   }
    190 }
    191 
    192 export function updateDisplaySimple(cm, viewport) {
    193   let update = new DisplayUpdate(cm, viewport)
    194   if (updateDisplayIfNeeded(cm, update)) {
    195     updateHeightsInViewport(cm)
    196     postUpdateDisplay(cm, update)
    197     let barMeasure = measureForScrollbars(cm)
    198     updateSelection(cm)
    199     updateScrollbars(cm, barMeasure)
    200     setDocumentHeight(cm, barMeasure)
    201     update.finish()
    202   }
    203 }
    204 
    205 // Sync the actual display DOM structure with display.view, removing
    206 // nodes for lines that are no longer in view, and creating the ones
    207 // that are not there yet, and updating the ones that are out of
    208 // date.
    209 function patchDisplay(cm, updateNumbersFrom, dims) {
    210   let display = cm.display, lineNumbers = cm.options.lineNumbers
    211   let container = display.lineDiv, cur = container.firstChild
    212 
    213   function rm(node) {
    214     let next = node.nextSibling
    215     // Works around a throw-scroll bug in OS X Webkit
    216     if (webkit && mac && cm.display.currentWheelTarget == node)
    217       node.style.display = "none"
    218     else
    219       node.parentNode.removeChild(node)
    220     return next
    221   }
    222 
    223   let view = display.view, lineN = display.viewFrom
    224   // Loop over the elements in the view, syncing cur (the DOM nodes
    225   // in display.lineDiv) with the view as we go.
    226   for (let i = 0; i < view.length; i++) {
    227     let lineView = view[i]
    228     if (lineView.hidden) {
    229     } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet
    230       let node = buildLineElement(cm, lineView, lineN, dims)
    231       container.insertBefore(node, cur)
    232     } else { // Already drawn
    233       while (cur != lineView.node) cur = rm(cur)
    234       let updateNumber = lineNumbers && updateNumbersFrom != null &&
    235         updateNumbersFrom <= lineN && lineView.lineNumber
    236       if (lineView.changes) {
    237         if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false
    238         updateLineForChanges(cm, lineView, lineN, dims)
    239       }
    240       if (updateNumber) {
    241         removeChildren(lineView.lineNumber)
    242         lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN)))
    243       }
    244       cur = lineView.node.nextSibling
    245     }
    246     lineN += lineView.size
    247   }
    248   while (cur) cur = rm(cur)
    249 }
    250 
    251 export function updateGutterSpace(cm) {
    252   let width = cm.display.gutters.offsetWidth
    253   cm.display.sizer.style.marginLeft = width + "px"
    254 }
    255 
    256 export function setDocumentHeight(cm, measure) {
    257   cm.display.sizer.style.minHeight = measure.docHeight + "px"
    258   cm.display.heightForcer.style.top = measure.docHeight + "px"
    259   cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px"
    260 }