mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2025-11-14 01:06:09 +01:00
save work
This commit is contained in:
110
static/filemanager/src/display/Display.js
Normal file
110
static/filemanager/src/display/Display.js
Normal 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)
|
||||
}
|
||||
47
static/filemanager/src/display/focus.js
Normal file
47
static/filemanager/src/display/focus.js
Normal 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)
|
||||
}
|
||||
44
static/filemanager/src/display/gutters.js
Normal file
44
static/filemanager/src/display/gutters.js
Normal 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)
|
||||
}
|
||||
55
static/filemanager/src/display/highlight_worker.js
Normal file
55
static/filemanager/src/display/highlight_worker.js
Normal 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")
|
||||
})
|
||||
}
|
||||
48
static/filemanager/src/display/line_numbers.js
Normal file
48
static/filemanager/src/display/line_numbers.js
Normal 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
|
||||
}
|
||||
22
static/filemanager/src/display/mode_state.js
Normal file
22
static/filemanager/src/display/mode_state.js
Normal 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)
|
||||
}
|
||||
205
static/filemanager/src/display/operations.js
Normal file
205
static/filemanager/src/display/operations.js
Normal 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) }
|
||||
}
|
||||
}
|
||||
115
static/filemanager/src/display/scroll_events.js
Normal file
115
static/filemanager/src/display/scroll_events.js
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
193
static/filemanager/src/display/scrollbars.js
Normal file
193
static/filemanager/src/display/scrollbars.js
Normal 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)
|
||||
}
|
||||
185
static/filemanager/src/display/scrolling.js
Normal file
185
static/filemanager/src/display/scrolling.js
Normal 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)
|
||||
}
|
||||
161
static/filemanager/src/display/selection.js
Normal file
161
static/filemanager/src/display/selection.js
Normal 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"
|
||||
}
|
||||
263
static/filemanager/src/display/update_display.js
Normal file
263
static/filemanager/src/display/update_display.js
Normal 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"
|
||||
}
|
||||
188
static/filemanager/src/display/update_line.js
Normal file
188
static/filemanager/src/display/update_line.js
Normal 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"
|
||||
}
|
||||
}
|
||||
76
static/filemanager/src/display/update_lines.js
Normal file
76
static/filemanager/src/display/update_lines.js
Normal 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)}
|
||||
}
|
||||
153
static/filemanager/src/display/view_tracking.js
Normal file
153
static/filemanager/src/display/view_tracking.js
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user