Index: Source/devtools/front_end/cm/codemirror.js |
diff --git a/Source/devtools/front_end/cm/codemirror.js b/Source/devtools/front_end/cm/codemirror.js |
index 390cb3786647dce8b05f641f40e7d85bba00291a..c8f48092f44572a844ccc3502fb86668f43fe5ef 100644 |
--- a/Source/devtools/front_end/cm/codemirror.js |
+++ b/Source/devtools/front_end/cm/codemirror.js |
@@ -108,6 +108,7 @@ |
for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt)) |
optionHandlers[opt](cm, options[opt], Init); |
+ maybeUpdateLineNumberWidth(cm); |
for (var i = 0; i < initHooks.length; ++i) initHooks[i](cm); |
}); |
} |
@@ -467,18 +468,18 @@ |
} |
// Compute the lines that are visible in a given viewport (defaults |
- // the the current scroll position). viewPort may contain top, |
+ // the the current scroll position). viewport may contain top, |
// height, and ensure (see op.scrollToPos) properties. |
- function visibleLines(display, doc, viewPort) { |
- var top = viewPort && viewPort.top != null ? Math.max(0, viewPort.top) : display.scroller.scrollTop; |
+ function visibleLines(display, doc, viewport) { |
+ var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop; |
top = Math.floor(top - paddingTop(display)); |
- var bottom = viewPort && viewPort.bottom != null ? viewPort.bottom : top + display.wrapper.clientHeight; |
+ var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight; |
var 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) { |
- var ensureFrom = viewPort.ensure.from.line, ensureTo = viewPort.ensure.to.line; |
+ if (viewport && viewport.ensure) { |
+ var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line; |
if (ensureFrom < from) |
return {from: ensureFrom, |
to: lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight)}; |
@@ -543,83 +544,46 @@ |
// DISPLAY DRAWING |
- // Updates the display, selection, and scrollbars, using the |
- // information in display.view to find out which nodes are no longer |
- // up-to-date. Tries to bail out early when no changes are needed, |
- // unless forced is true. |
- // Returns true if an actual update happened, false otherwise. |
- function updateDisplay(cm, viewPort, forced) { |
- var oldFrom = cm.display.viewFrom, oldTo = cm.display.viewTo, updated; |
- var visible = visibleLines(cm.display, cm.doc, viewPort); |
- for (var first = true;; first = false) { |
- var oldWidth = cm.display.scroller.clientWidth; |
- if (!updateDisplayInner(cm, visible, forced)) break; |
- updated = true; |
- |
- // If the max line changed since it was last measured, measure it, |
- // and ensure the document's width matches it. |
- if (cm.display.maxLineChanged && !cm.options.lineWrapping) |
- adjustContentWidth(cm); |
- |
- var barMeasure = measureForScrollbars(cm); |
- updateSelection(cm); |
- setDocumentHeight(cm, barMeasure); |
- updateScrollbars(cm, barMeasure); |
- if (webkit && cm.options.lineWrapping) |
- checkForWebkitWidthBug(cm, barMeasure); // (Issue #2420) |
- if (webkit && barMeasure.scrollWidth > barMeasure.clientWidth && |
- barMeasure.scrollWidth < barMeasure.clientWidth + 1 && |
- !hScrollbarTakesSpace(cm)) |
- updateScrollbars(cm); // (Issue #2562) |
- if (first && cm.options.lineWrapping && oldWidth != cm.display.scroller.clientWidth) { |
- forced = true; |
- continue; |
- } |
- forced = false; |
- |
- // Clip forced viewport to actual scrollable area. |
- if (viewPort && viewPort.top != null) |
- viewPort = {top: Math.min(barMeasure.docHeight - scrollerCutOff - barMeasure.clientHeight, viewPort.top)}; |
- // Updated line heights might result in the drawn area not |
- // actually covering the viewport. Keep looping until it does. |
- visible = visibleLines(cm.display, cm.doc, viewPort); |
- if (visible.from >= cm.display.viewFrom && visible.to <= cm.display.viewTo) |
- break; |
- } |
+ function DisplayUpdate(cm, viewport, force) { |
+ var display = cm.display; |
- cm.display.updateLineNumbers = null; |
- if (updated) { |
- signalLater(cm, "update", cm); |
- if (cm.display.viewFrom != oldFrom || cm.display.viewTo != oldTo) |
- signalLater(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); |
- } |
- return updated; |
+ 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.oldViewFrom = display.viewFrom; this.oldViewTo = display.viewTo; |
+ this.oldScrollerWidth = display.scroller.clientWidth; |
+ this.force = force; |
+ this.dims = getDimensions(cm); |
} |
// Does the actual updating of the line display. Bails out |
// (returning false) when there is nothing to be done and forced is |
// false. |
- function updateDisplayInner(cm, visible, forced) { |
+ function updateDisplayIfNeeded(cm, update) { |
var display = cm.display, doc = cm.doc; |
- if (!display.wrapper.offsetWidth) { |
+ if (update.editorIsHidden) { |
resetView(cm); |
- return; |
+ return false; |
} |
// Bail out if the visible area is already rendered and nothing changed. |
- if (!forced && visible.from >= display.viewFrom && visible.to <= display.viewTo && |
+ if (!update.force && |
+ update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && |
(display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && |
countDirtyView(cm) == 0) |
- return; |
+ return false; |
- if (maybeUpdateLineNumberWidth(cm)) |
+ if (maybeUpdateLineNumberWidth(cm)) { |
resetView(cm); |
- var dims = getDimensions(cm); |
+ update.dims = getDimensions(cm); |
+ } |
// Compute a suitable new viewport (from & to) |
var end = doc.first + doc.size; |
- var from = Math.max(visible.from - cm.options.viewportMargin, doc.first); |
- var to = Math.min(end, visible.to + cm.options.viewportMargin); |
+ var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first); |
+ var 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) { |
@@ -628,7 +592,7 @@ |
} |
var different = from != display.viewFrom || to != display.viewTo || |
- display.lastSizeC != display.wrapper.clientHeight; |
+ display.lastSizeC != update.wrapperHeight; |
adjustView(cm, from, to); |
display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)); |
@@ -636,13 +600,15 @@ |
cm.display.mover.style.top = display.viewOffset + "px"; |
var toUpdate = countDirtyView(cm); |
- if (!different && toUpdate == 0 && !forced) return; |
+ if (!different && toUpdate == 0 && !update.force && |
+ (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. |
var focused = activeElt(); |
if (toUpdate > 4) display.lineDiv.style.display = "none"; |
- patchDisplay(cm, display.updateLineNumbers, dims); |
+ patchDisplay(cm, display.updateLineNumbers, update.dims); |
if (toUpdate > 4) display.lineDiv.style.display = ""; |
// There might have been a widget with a focused element that got |
// hidden or updated, if so re-focus it. |
@@ -654,24 +620,55 @@ |
removeChildren(display.selectionDiv); |
if (different) { |
- display.lastSizeC = display.wrapper.clientHeight; |
+ display.lastSizeC = update.wrapperHeight; |
startWorker(cm, 400); |
} |
- updateHeightsInViewport(cm); |
+ display.updateLineNumbers = null; |
return true; |
} |
- function adjustContentWidth(cm) { |
- var display = cm.display; |
- var width = measureChar(cm, display.maxLine, display.maxLine.text.length).left; |
- display.maxLineChanged = false; |
- var minWidth = Math.max(0, width + 3); |
- var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + minWidth + scrollerCutOff - display.scroller.clientWidth); |
- display.sizer.style.minWidth = minWidth + "px"; |
- if (maxScrollLeft < cm.doc.scrollLeft) |
- setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true); |
+ function postUpdateDisplay(cm, update) { |
+ var force = update.force, viewport = update.viewport; |
+ for (var first = true;; first = false) { |
+ if (first && cm.options.lineWrapping && update.oldScrollerWidth != cm.display.scroller.clientWidth) { |
+ force = true; |
+ } else { |
+ force = false; |
+ // Clip forced viewport to actual scrollable area. |
+ if (viewport && viewport.top != null) |
+ viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - scrollerCutOff - |
+ cm.display.scroller.clientHeight, 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; |
+ } |
+ if (!updateDisplayIfNeeded(cm, update)) break; |
+ updateHeightsInViewport(cm); |
+ var barMeasure = measureForScrollbars(cm); |
+ updateSelection(cm); |
+ setDocumentHeight(cm, barMeasure); |
+ updateScrollbars(cm, barMeasure); |
+ } |
+ |
+ signalLater(cm, "update", cm); |
+ if (cm.display.viewFrom != update.oldViewFrom || cm.display.viewTo != update.oldViewTo) |
+ signalLater(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); |
+ } |
+ |
+ function updateDisplaySimple(cm, viewport) { |
+ var update = new DisplayUpdate(cm, viewport); |
+ if (updateDisplayIfNeeded(cm, update)) { |
+ updateHeightsInViewport(cm); |
+ postUpdateDisplay(cm, update); |
+ var barMeasure = measureForScrollbars(cm); |
+ updateSelection(cm); |
+ setDocumentHeight(cm, barMeasure); |
+ updateScrollbars(cm, barMeasure); |
+ } |
} |
function setDocumentHeight(cm, measure) { |
@@ -1257,10 +1254,10 @@ |
// SELECTION DRAWING |
// Redraw the selection and/or cursor |
- function updateSelection(cm) { |
- var display = cm.display, doc = cm.doc; |
- var curFragment = document.createDocumentFragment(); |
- var selFragment = document.createDocumentFragment(); |
+ function drawSelection(cm) { |
+ var display = cm.display, doc = cm.doc, result = {}; |
+ var curFragment = result.cursors = document.createDocumentFragment(); |
+ var selFragment = result.selection = document.createDocumentFragment(); |
for (var i = 0; i < doc.sel.ranges.length; i++) { |
var range = doc.sel.ranges[i]; |
@@ -1275,16 +1272,26 @@ |
if (cm.options.moveInputWithCursor) { |
var headPos = cursorCoords(cm, doc.sel.primary().head, "div"); |
var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect(); |
- var top = Math.max(0, Math.min(display.wrapper.clientHeight - 10, |
- headPos.top + lineOff.top - wrapOff.top)); |
- var left = Math.max(0, Math.min(display.wrapper.clientWidth - 10, |
- headPos.left + lineOff.left - wrapOff.left)); |
- display.inputDiv.style.top = top + "px"; |
- display.inputDiv.style.left = left + "px"; |
+ result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, |
+ headPos.top + lineOff.top - wrapOff.top)); |
+ result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, |
+ headPos.left + lineOff.left - wrapOff.left)); |
+ } |
+ |
+ return result; |
+ } |
+ |
+ function showSelection(cm, drawn) { |
+ removeChildrenAndAdd(cm.display.cursorDiv, drawn.cursors); |
+ removeChildrenAndAdd(cm.display.selectionDiv, drawn.selection); |
+ if (drawn.teTop != null) { |
+ cm.display.inputDiv.style.top = drawn.teTop + "px"; |
+ cm.display.inputDiv.style.left = drawn.teLeft + "px"; |
} |
+ } |
- removeChildrenAndAdd(display.cursorDiv, curFragment); |
- removeChildrenAndAdd(display.selectionDiv, selFragment); |
+ function updateSelection(cm) { |
+ showSelection(cm, drawSelection(cm)); |
} |
// Draws a cursor for the given range |
@@ -1408,8 +1415,8 @@ |
if (doc.frontier >= cm.display.viewTo) return; |
var end = +new Date + cm.options.workTime; |
var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)); |
+ var changedLines = []; |
- runInOp(cm, function() { |
doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function(line) { |
if (doc.frontier >= cm.display.viewFrom) { // Visible |
var oldStyles = line.styles; |
@@ -1421,7 +1428,7 @@ |
var ischange = !oldStyles || oldStyles.length != line.styles.length || |
oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass); |
for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i]; |
- if (ischange) regLineChange(cm, doc.frontier, "text"); |
+ if (ischange) changedLines.push(doc.frontier); |
line.stateAfter = copyState(doc.mode, state); |
} else { |
processLine(cm, line.text, state); |
@@ -1433,6 +1440,9 @@ |
return true; |
} |
}); |
+ if (changedLines.length) runInOp(cm, function() { |
+ for (var i = 0; i < changedLines.length; i++) |
+ regLineChange(cm, changedLines[i], "text"); |
}); |
} |
@@ -1665,6 +1675,8 @@ |
rect = nullRect; |
} |
+ if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.measure, rect); |
+ |
var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top; |
var mid = (rtop + rbot) / 2; |
var heights = prepared.view.measure.heights; |
@@ -1676,9 +1688,22 @@ |
top: top, bottom: bot}; |
if (!rect.left && !rect.right) result.bogus = true; |
if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; } |
+ |
return result; |
} |
+ // Work around problem with bounding client rects on ranges being |
+ // returned incorrectly when zoomed on IE10 and below. |
+ function maybeUpdateRectForZooming(measure, rect) { |
+ if (!window.screen || screen.logicalXDPI == null || |
+ screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) |
+ return rect; |
+ var scaleX = screen.logicalXDPI / screen.deviceXDPI; |
+ var scaleY = screen.logicalYDPI / screen.deviceYDPI; |
+ return {left: rect.left * scaleX, right: rect.right * scaleX, |
+ top: rect.top * scaleY, bottom: rect.bottom * scaleY}; |
+ } |
+ |
function clearLineMeasurementCacheFor(lineView) { |
if (lineView.measure) { |
lineView.measure.cache = {}; |
@@ -1911,10 +1936,13 @@ |
// error-prone). Instead, display updates are batched and then all |
// combined and executed at once. |
+ var operationGroup = null; |
+ |
var nextOpId = 0; |
// Start a new operation. |
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 |
@@ -1922,33 +1950,134 @@ |
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 |
id: ++nextOpId // Unique ID |
}; |
- if (!delayedCallbackDepth++) delayedCallbacks = []; |
+ if (operationGroup) { |
+ operationGroup.ops.push(cm.curOp); |
+ } else { |
+ cm.curOp.ownsGroup = operationGroup = { |
+ ops: [cm.curOp], |
+ delayedCallbacks: [] |
+ }; |
+ } |
+ } |
+ |
+ function fireCallbacksForOps(group) { |
+ // Calls delayed callbacks and cursorActivity handlers until no |
+ // new ones appear |
+ var callbacks = group.delayedCallbacks, i = 0; |
+ do { |
+ for (; i < callbacks.length; i++) |
+ callbacks[i](); |
+ for (var j = 0; j < group.ops.length; j++) { |
+ var op = group.ops[j]; |
+ if (op.cursorActivityHandlers) |
+ while (op.cursorActivityCalled < op.cursorActivityHandlers.length) |
+ op.cursorActivityHandlers[op.cursorActivityCalled++](op.cm); |
+ } |
+ } while (i < callbacks.length); |
} |
// Finish an operation, updating the display and signalling delayed events |
function endOperation(cm) { |
- var op = cm.curOp, doc = cm.doc, display = cm.display; |
- cm.curOp = null; |
- |
+ var op = cm.curOp, group = op.ownsGroup; |
+ if (!group) return; |
+ |
+ try { fireCallbacksForOps(group); } |
+ finally { |
+ operationGroup = null; |
+ for (var 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) { |
+ var ops = group.ops; |
+ for (var i = 0; i < ops.length; i++) // Read DOM |
+ endOperation_R1(ops[i]); |
+ for (var i = 0; i < ops.length; i++) // Write DOM (maybe) |
+ endOperation_W1(ops[i]); |
+ for (var i = 0; i < ops.length; i++) // Read DOM |
+ endOperation_R2(ops[i]); |
+ for (var i = 0; i < ops.length; i++) // Write DOM (maybe) |
+ endOperation_W2(ops[i]); |
+ for (var i = 0; i < ops.length; i++) // Read DOM |
+ endOperation_finish(ops[i]); |
+ } |
+ |
+ function endOperation_R1(op) { |
+ var cm = op.cm, display = cm.display; |
if (op.updateMaxLine) findMaxLine(cm); |
- // If it looks like an update might be needed, call updateDisplay |
- if (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) { |
- var updated = updateDisplay(cm, {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate); |
- if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop; |
+ 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) { |
+ var 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; |
+ op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo + |
+ scrollerCutOff - display.scroller.clientWidth); |
} |
- // If no update was run, but the selection changed, redraw that. |
- if (!updated && op.selectionChanged) updateSelection(cm); |
- if (!updated && op.startHeight != cm.doc.height) updateScrollbars(cm); |
+ |
+ if (op.updatedDisplay || op.selectionChanged) |
+ op.newSelectionNodes = drawSelection(cm); |
+ } |
+ |
+ function endOperation_W2(op) { |
+ var 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; |
+ } |
+ |
+ if (op.newSelectionNodes) |
+ showSelection(cm, op.newSelectionNodes); |
+ if (op.updatedDisplay) |
+ setDocumentHeight(cm, op.barMeasure); |
+ if (op.updatedDisplay || op.startHeight != cm.doc.height) |
+ updateScrollbars(cm, op.barMeasure); |
+ |
+ if (op.selectionChanged) restartBlink(cm); |
+ |
+ if (cm.state.focused && op.updateInput) |
+ resetInput(cm, op.typing); |
+ } |
+ |
+ function endOperation_finish(op) { |
+ var cm = op.cm, display = cm.display, doc = cm.doc; |
+ |
+ if (op.adjustWidthTo != null && Math.abs(op.barMeasure.scrollWidth - cm.display.scroller.scrollWidth) > 1) |
+ updateScrollbars(cm); |
+ |
+ 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)) |
@@ -1966,16 +2095,11 @@ |
} |
// If we need to scroll a specific position into view, do so. |
if (op.scrollToPos) { |
- var coords = scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos.from), |
- clipPos(cm.doc, op.scrollToPos.to), op.scrollToPos.margin); |
+ var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), |
+ clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin); |
if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords); |
} |
- if (op.selectionChanged) restartBlink(cm); |
- |
- if (cm.state.focused && op.updateInput) |
- resetInput(cm, op.typing); |
- |
// Fire events for markers that are hidden/unidden by editing or |
// undoing |
var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; |
@@ -1984,18 +2108,22 @@ |
if (unhidden) for (var i = 0; i < unhidden.length; ++i) |
if (unhidden[i].lines.length) signal(unhidden[i], "unhide"); |
- var delayed; |
- if (!--delayedCallbackDepth) { |
- delayed = delayedCallbacks; |
- delayedCallbacks = null; |
+ if (display.wrapper.offsetHeight) |
+ doc.scrollTop = cm.display.scroller.scrollTop; |
+ |
+ // Apply workaround for two webkit bugs |
+ if (op.updatedDisplay && webkit) { |
+ if (cm.options.lineWrapping) |
+ checkForWebkitWidthBug(cm, op.barMeasure); // (Issue #2420) |
+ if (op.barMeasure.scrollWidth > op.barMeasure.clientWidth && |
+ op.barMeasure.scrollWidth < op.barMeasure.clientWidth + 1 && |
+ !hScrollbarTakesSpace(cm)) |
+ updateScrollbars(cm); // (Issue #2562) |
} |
+ |
// Fire change events, and delayed event handlers |
if (op.changeObjs) |
signal(cm, "changes", cm, op.changeObjs); |
- if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i](); |
- if (op.cursorActivityHandlers) |
- for (var i = 0; i < op.cursorActivityHandlers.length; i++) |
- op.cursorActivityHandlers[i](cm); |
} |
// Run the given function in an operation |
@@ -2247,6 +2375,11 @@ |
cm.display.poll.set(20, p); |
} |
+ // This will be set to an array of strings when copying, so that, |
+ // when pasting, we know what kind of selections the copied text |
+ // was made out of. |
+ var lastCopied = null; |
+ |
// Read input from the textarea, and update the document to match. |
// When something is selected, it is present in the textarea, and |
// selected (unless it is huge, in which case a placeholder is |
@@ -2269,8 +2402,11 @@ |
var text = input.value; |
// If nothing changed, bail. |
if (text == prevInput && !cm.somethingSelected()) return false; |
- // Work around nonsensical selection resetting in IE9/10 |
- if (ie && ie_version >= 9 && cm.display.inputHasSelection === text) { |
+ // Work around nonsensical selection resetting in IE9/10, and |
+ // inexplicable appearance of private area unicode characters on |
+ // some key combos in Mac (#2689). |
+ if (ie && ie_version >= 9 && cm.display.inputHasSelection === text || |
+ mac && /[\uf700-\uf7ff]/.test(text)) { |
resetInput(cm); |
return false; |
} |
@@ -2287,7 +2423,13 @@ |
var inserted = text.slice(same), textLines = splitLines(inserted); |
// When pasing N lines into N selections, insert one line per selection |
- var multiPaste = cm.state.pasteIncoming && textLines.length > 1 && doc.sel.ranges.length == textLines.length; |
+ var multiPaste = null; |
+ if (cm.state.pasteIncoming && doc.sel.ranges.length > 1) { |
+ if (lastCopied && lastCopied.join("\n") == inserted) |
+ multiPaste = doc.sel.ranges.length % lastCopied.length == 0 && map(lastCopied, splitLines); |
+ else if (textLines.length == doc.sel.ranges.length) |
+ multiPaste = map(textLines, function(l) { return [l]; }); |
+ } |
// Normal behavior is to insert the new text into every selection |
for (var i = doc.sel.ranges.length - 1; i >= 0; i--) { |
@@ -2300,7 +2442,7 @@ |
else if (cm.state.overwrite && range.empty() && !cm.state.pasteIncoming) |
to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); |
var updateInput = cm.curOp.updateInput; |
- var changeEvent = {from: from, to: to, text: multiPaste ? [textLines[i]] : textLines, |
+ var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines, |
origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"}; |
makeChange(cm.doc, changeEvent); |
signalLater(cm, "inputRead", cm, changeEvent); |
@@ -2421,7 +2563,7 @@ |
// Prevent wrapper from ever scrolling |
on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); |
- on(d.input, "keyup", operation(cm, onKeyUp)); |
+ on(d.input, "keyup", function(e) { onKeyUp.call(cm, e); }); |
on(d.input, "input", function() { |
if (ie && ie_version >= 9 && cm.display.inputHasSelection) cm.display.inputHasSelection = null; |
fastPoll(cm); |
@@ -2467,27 +2609,29 @@ |
function prepareCopyCut(e) { |
if (cm.somethingSelected()) { |
+ lastCopied = cm.getSelections(); |
if (d.inaccurateSelection) { |
d.prevInput = ""; |
d.inaccurateSelection = false; |
- d.input.value = cm.getSelection(); |
+ d.input.value = lastCopied.join("\n"); |
selectInput(d.input); |
} |
} else { |
- var text = "", ranges = []; |
+ var text = [], ranges = []; |
for (var i = 0; i < cm.doc.sel.ranges.length; i++) { |
var line = cm.doc.sel.ranges[i].head.line; |
var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}; |
ranges.push(lineRange); |
- text += cm.getRange(lineRange.anchor, lineRange.head); |
+ text.push(cm.getRange(lineRange.anchor, lineRange.head)); |
} |
if (e.type == "cut") { |
cm.setSelections(ranges, null, sel_dontScroll); |
} else { |
d.prevInput = ""; |
- d.input.value = text; |
+ d.input.value = text.join("\n"); |
selectInput(d.input); |
} |
+ lastCopied = text; |
} |
if (e.type == "cut") cm.state.cutIncoming = true; |
} |
@@ -2885,10 +3029,10 @@ |
function setScrollTop(cm, val) { |
if (Math.abs(cm.doc.scrollTop - val) < 2) return; |
cm.doc.scrollTop = val; |
- if (!gecko) updateDisplay(cm, {top: val}); |
+ if (!gecko) updateDisplaySimple(cm, {top: val}); |
if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val; |
if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val; |
- if (gecko) updateDisplay(cm); |
+ if (gecko) updateDisplaySimple(cm); |
startWorker(cm, 100); |
} |
// Sync scroller and scrollbar, ensure the gutter elements are |
@@ -2971,7 +3115,7 @@ |
var 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); |
- updateDisplay(cm, {top: top, bottom: bot}); |
+ updateDisplaySimple(cm, {top: top, bottom: bot}); |
} |
if (wheelSamples < 20) { |
@@ -3114,13 +3258,13 @@ |
} |
function onKeyUp(e) { |
- if (signalDOMEvent(this, e)) return; |
if (e.keyCode == 16) this.doc.sel.shift = false; |
+ signalDOMEvent(this, e); |
} |
function onKeyPress(e) { |
var cm = this; |
- if (signalDOMEvent(cm, e) || e.ctrlKey || mac && e.metaKey) return; |
+ if (signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) return; |
var keyCode = e.keyCode, charCode = e.charCode; |
if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} |
if (((presto && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return; |
@@ -3184,7 +3328,9 @@ |
"px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " + |
(ie ? "rgba(255, 255, 255, .05)" : "transparent") + |
"; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; |
+ if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2712) |
focusInput(cm); |
+ if (webkit) window.scrollTo(null, oldScrollY); |
resetInput(cm); |
// Adds "Select all" to context menu in FF |
if (!cm.somethingSelected()) display.input.value = display.prevInput = " "; |
@@ -3414,9 +3560,9 @@ |
antiChanges.push(historyChangeFromChange(doc, change)); |
- var after = i ? computeSelAfterChange(doc, change, null) : lst(source); |
+ var after = i ? computeSelAfterChange(doc, change) : lst(source); |
makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); |
- if (!i && doc.cm) doc.cm.scrollIntoView(change); |
+ if (!i && doc.cm) doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}); |
var rebased = []; |
// Propagate to the linked documents |
@@ -3473,7 +3619,7 @@ |
change.removed = getBetween(doc, change.from, change.to); |
- if (!selAfter) selAfter = computeSelAfterChange(doc, change, null); |
+ if (!selAfter) selAfter = computeSelAfterChange(doc, change); |
if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans); |
else updateDoc(doc, change, spans); |
setSelectionNoUndo(doc, selAfter, sel_dontScroll); |
@@ -3605,6 +3751,7 @@ |
if (y1 < 0) y1 = 0; |
var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop; |
var screen = display.scroller.clientHeight - scrollerCutOff, result = {}; |
+ if (y2 - y1 > screen) y2 = y1 + screen; |
var docBottom = cm.doc.height + paddingVert(display); |
var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin; |
if (y1 < screentop) { |
@@ -3615,16 +3762,16 @@ |
} |
var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft; |
- var screenw = display.scroller.clientWidth - scrollerCutOff; |
- x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth; |
- var gutterw = display.gutters.offsetWidth; |
- var atLeft = x1 < gutterw + 10; |
- if (x1 < screenleft + gutterw || atLeft) { |
- if (atLeft) x1 = 0; |
- result.scrollLeft = Math.max(0, x1 - 10 - gutterw); |
- } else if (x2 > screenw + screenleft - 3) { |
- result.scrollLeft = x2 + 10 - screenw; |
- } |
+ var screenw = display.scroller.clientWidth - scrollerCutOff - display.gutters.offsetWidth; |
+ var tooWide = x2 - x1 > screenw; |
+ if (tooWide) x2 = y1 + screen; |
+ if (x1 < 10) |
+ result.scrollLeft = 0; |
+ else if (x1 < screenleft) |
+ result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10)); |
+ else if (x2 > screenw + screenleft - 3) |
+ result.scrollLeft = x2 + (tooWide ? 0 : 10) - screenw; |
+ |
return result; |
} |
@@ -3680,7 +3827,7 @@ |
if (how == "smart") { |
// Fall back to "prev" when the mode doesn't have an indentation |
// method. |
- if (!cm.doc.mode.indent) how = "prev"; |
+ if (!doc.mode.indent) how = "prev"; |
else state = getStateBefore(cm, n); |
} |
@@ -3692,8 +3839,8 @@ |
indentation = 0; |
how = "not"; |
} else if (how == "smart") { |
- indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); |
- if (indentation == Pass) { |
+ indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); |
+ if (indentation == Pass || indentation > 150) { |
if (!aggressive) return; |
how = "prev"; |
} |
@@ -3716,7 +3863,7 @@ |
if (pos < indentation) indentString += spaceStr(indentation - pos); |
if (indentString != curSpaceString) { |
- replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); |
+ replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); |
} else { |
// Ensure that, if the cursor was in the whitespace at the start |
// of the line, it is moved to the end of that space. |
@@ -3933,11 +4080,14 @@ |
for (var i = 0; i < ranges.length; i++) { |
var range = ranges[i]; |
if (!range.empty()) { |
- var start = Math.max(end, range.from().line); |
- var to = range.to(); |
+ var from = range.from(), to = range.to(); |
+ var start = Math.max(end, from.line); |
end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1; |
for (var j = start; j < end; ++j) |
indentLine(this, j, how); |
+ var newRanges = this.doc.sel.ranges; |
+ if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) |
+ replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to())); |
} else if (range.head.line > end) { |
indentLine(this, range.head.line, how, true); |
end = range.head.line; |
@@ -4133,7 +4283,7 @@ |
triggerOnKeyDown: methodOp(onKeyDown), |
triggerOnKeyPress: methodOp(onKeyPress), |
- triggerOnKeyUp: methodOp(onKeyUp), |
+ triggerOnKeyUp: onKeyUp, |
execCommand: function(cmd) { |
if (commands.hasOwnProperty(cmd)) |
@@ -4333,7 +4483,7 @@ |
clearCaches(cm); |
regChange(cm); |
}, true); |
- option("specialChars", /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\ufeff]/g, function(cm, val) { |
+ option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val) { |
cm.options.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); |
cm.refresh(); |
}, true); |
@@ -4568,6 +4718,20 @@ |
return {from: Pos(range.from().line, 0), to: range.from()}; |
}); |
}, |
+ delWrappedLineLeft: function(cm) { |
+ deleteNearSelection(cm, function(range) { |
+ var top = cm.charCoords(range.head, "div").top + 5; |
+ var leftPos = cm.coordsChar({left: 0, top: top}, "div"); |
+ return {from: leftPos, to: range.from()}; |
+ }); |
+ }, |
+ delWrappedLineRight: function(cm) { |
+ deleteNearSelection(cm, function(range) { |
+ var top = cm.charCoords(range.head, "div").top + 5; |
+ var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); |
+ return {from: range.from(), to: rightPos }; |
+ }); |
+ }, |
undo: function(cm) {cm.undo();}, |
redo: function(cm) {cm.redo();}, |
undoSelection: function(cm) {cm.undoSelection();}, |
@@ -4580,15 +4744,7 @@ |
}, |
goLineStartSmart: function(cm) { |
cm.extendSelectionsBy(function(range) { |
- var start = lineStart(cm, range.head.line); |
- var line = cm.getLineHandle(start.line); |
- var order = getOrder(line); |
- if (!order || order[0].level == 0) { |
- var firstNonWS = Math.max(0, line.text.search(/\S/)); |
- var inWS = range.head.line == start.line && range.head.ch <= firstNonWS && range.head.ch; |
- return Pos(start.line, inWS ? 0 : firstNonWS); |
- } |
- return start; |
+ return lineStartSmart(cm, range.head); |
}, {origin: "+move", bias: 1}); |
}, |
goLineEnd: function(cm) { |
@@ -4607,6 +4763,14 @@ |
return cm.coordsChar({left: 0, top: top}, "div"); |
}, sel_move); |
}, |
+ goLineLeftSmart: function(cm) { |
+ cm.extendSelectionsBy(function(range) { |
+ var top = cm.charCoords(range.head, "div").top + 5; |
+ var pos = cm.coordsChar({left: 0, top: top}, "div"); |
+ if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm, range.head); |
+ return pos; |
+ }, sel_move); |
+ }, |
goLineUp: function(cm) {cm.moveV(-1, "line");}, |
goLineDown: function(cm) {cm.moveV(1, "line");}, |
goPageUp: function(cm) {cm.moveV(-1, "page");}, |
@@ -4705,11 +4869,11 @@ |
}; |
keyMap.macDefault = { |
"Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", |
- "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", |
- "Alt-Right": "goGroupRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delGroupBefore", |
+ "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", |
+ "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", |
"Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", |
"Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", |
- "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delLineLeft", |
+ "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", |
"Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", |
fallthrough: ["basic", "emacsy"] |
}; |
@@ -6344,7 +6508,7 @@ |
}, |
changeGeneration: function(forceSplit) { |
if (forceSplit) |
- this.history.lastOp = this.history.lastOrigin = null; |
+ this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null; |
return this.history.generation; |
}, |
isClean: function (gen) { |
@@ -6661,7 +6825,7 @@ |
// Used to track when changes can be merged into a single undo |
// event |
this.lastModTime = this.lastSelTime = 0; |
- this.lastOp = null; |
+ this.lastOp = this.lastSelOp = null; |
this.lastOrigin = this.lastSelOrigin = null; |
// Used by the isClean() method |
this.generation = this.maxGeneration = startGen || 1; |
@@ -6739,7 +6903,7 @@ |
hist.done.push(selAfter); |
hist.generation = ++hist.maxGeneration; |
hist.lastModTime = hist.lastSelTime = time; |
- hist.lastOp = opId; |
+ hist.lastOp = hist.lastSelOp = opId; |
hist.lastOrigin = hist.lastSelOrigin = change.origin; |
if (!last) signal(doc, "historyAdded"); |
@@ -6765,7 +6929,7 @@ |
// the current, or the origins don't allow matching. Origins |
// starting with * are always merged, those starting with + are |
// merged when similar and close together in time. |
- if (opId == hist.lastOp || |
+ if (opId == hist.lastSelOp || |
(origin && hist.lastSelOrigin == origin && |
(hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || |
selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) |
@@ -6775,7 +6939,7 @@ |
hist.lastSelTime = +new Date; |
hist.lastSelOrigin = origin; |
- hist.lastOp = opId; |
+ hist.lastSelOp = opId; |
if (options && options.clearRedo !== false) |
clearSelectionEvents(hist.undone); |
} |
@@ -6960,6 +7124,8 @@ |
for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args); |
}; |
+ var orphanDelayedCallbacks = null; |
+ |
// Often, we want to signal events at a point where we are in the |
// middle of some work, but don't want the handler to start calling |
// other methods on the editor, which might be in an inconsistent |
@@ -6967,25 +7133,26 @@ |
// signalLater looks whether there are any handlers, and schedules |
// them to be executed when the last operation ends, or, if no |
// operation is active, when a timeout fires. |
- var delayedCallbacks, delayedCallbackDepth = 0; |
function signalLater(emitter, type /*, values...*/) { |
var arr = emitter._handlers && emitter._handlers[type]; |
if (!arr) return; |
- var args = Array.prototype.slice.call(arguments, 2); |
- if (!delayedCallbacks) { |
- ++delayedCallbackDepth; |
- delayedCallbacks = []; |
- setTimeout(fireDelayed, 0); |
+ var args = Array.prototype.slice.call(arguments, 2), list; |
+ if (operationGroup) { |
+ list = operationGroup.delayedCallbacks; |
+ } else if (orphanDelayedCallbacks) { |
+ list = orphanDelayedCallbacks; |
+ } else { |
+ list = orphanDelayedCallbacks = []; |
+ setTimeout(fireOrphanDelayed, 0); |
} |
function bnd(f) {return function(){f.apply(null, args);};}; |
for (var i = 0; i < arr.length; ++i) |
- delayedCallbacks.push(bnd(arr[i])); |
+ list.push(bnd(arr[i])); |
} |
- function fireDelayed() { |
- --delayedCallbackDepth; |
- var delayed = delayedCallbacks; |
- delayedCallbacks = null; |
+ function fireOrphanDelayed() { |
+ var delayed = orphanDelayedCallbacks; |
+ orphanDelayedCallbacks = null; |
for (var i = 0; i < delayed.length; ++i) delayed[i](); |
} |
@@ -7331,6 +7498,15 @@ |
return typeof e.oncopy == "function"; |
})(); |
+ var badZoomedRects = null; |
+ function hasBadZoomedRects(measure) { |
+ if (badZoomedRects != null) return badZoomedRects; |
+ var node = removeChildrenAndAdd(measure, elt("span", "x")); |
+ var normal = node.getBoundingClientRect(); |
+ var fromRange = range(node, 0, 1).getBoundingClientRect(); |
+ return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1; |
+ } |
+ |
// KEY NAMES |
var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", |
@@ -7393,6 +7569,17 @@ |
var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line); |
return Pos(lineN == null ? lineNo(line) : lineN, ch); |
} |
+ function lineStartSmart(cm, pos) { |
+ var start = lineStart(cm, pos.line); |
+ var line = getLine(cm.doc, start.line); |
+ var order = getOrder(line); |
+ if (!order || order[0].level == 0) { |
+ var firstNonWS = Math.max(0, line.text.search(/\S/)); |
+ var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch; |
+ return Pos(start.line, inWS ? 0 : firstNonWS); |
+ } |
+ return start; |
+ } |
function compareBidiLevel(order, a, b) { |
var linedir = order[0].level; |
@@ -7632,7 +7819,7 @@ |
// THE END |
- CodeMirror.version = "4.3.1"; |
+ CodeMirror.version = "4.4.1"; |
return CodeMirror; |
}); |