diff --git a/gulliver/js/codemirror/lib/codemirror.js b/gulliver/js/codemirror/lib/codemirror.js index 342fb6583..120e09bbf 100644 --- a/gulliver/js/codemirror/lib/codemirror.js +++ b/gulliver/js/codemirror/lib/codemirror.js @@ -1,4 +1,4 @@ -// CodeMirror version 3.11 +// CodeMirror version 3.16 // // CodeMirror is the only global var we claim window.CodeMirror = (function() { @@ -26,10 +26,11 @@ window.CodeMirror = (function() { // This is woefully incomplete. Suggestions for alternative methods welcome. var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent); var mac = ios || /Mac/.test(navigator.platform); - var windows = /windows/i.test(navigator.platform); + var windows = /win/i.test(navigator.platform); var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/); if (opera_version) opera_version = Number(opera_version[1]); + if (opera_version && opera_version >= 15) { opera = false; webkit = true; } // Some browsers use the wrong event properties to signal cmd/ctrl on OS X var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11)); var captureMiddleClick = gecko || (ie && !ie_lt9); @@ -94,12 +95,12 @@ window.CodeMirror = (function() { function makeDisplay(place, docStart) { var d = {}; - var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none;"); + var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none; font-size: 4px;"); if (webkit) input.style.width = "1000px"; else input.setAttribute("wrap", "off"); // if border: 0; -- iOS fails to open keyboard (issue #1287) if (ios) input.style.border = "1px solid black"; - input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); + input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false"); // Wraps and hides input textarea d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); @@ -107,8 +108,9 @@ window.CodeMirror = (function() { d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar"); d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar"); d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); + d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); // DIVs containing the selection and the actual code - d.lineDiv = elt("div"); + d.lineDiv = elt("div", null, "CodeMirror-code"); d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); // Blinky cursor, and element used to ensure cursor fits at the end of a line d.cursor = elt("div", "\u00a0", "CodeMirror-cursor"); @@ -128,14 +130,12 @@ window.CodeMirror = (function() { // Will contain the gutters, if any d.gutters = elt("div", null, "CodeMirror-gutters"); d.lineGutter = null; - // Helper element to properly size the gutter backgrounds - var scrollerInner = elt("div", [d.sizer, d.heightForcer, d.gutters], null, "position: relative; min-height: 100%"); // Provides scrolling - d.scroller = elt("div", [scrollerInner], "CodeMirror-scroll"); + 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.inputDiv, d.scrollbarH, d.scrollbarV, - d.scrollbarFiller, d.scroller], "CodeMirror"); + d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror"); // Work around IE7 z-index bug if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper); @@ -166,8 +166,6 @@ window.CodeMirror = (function() { d.pollingFast = false; // Self-resetting timeout for the poller d.poll = new Delayed(); - // True when a drag from the editor is active - d.draggingText = false; d.cachedCharWidth = d.cachedTextHeight = null; d.measureLineCache = []; @@ -216,7 +214,7 @@ window.CodeMirror = (function() { estimateLineHeights(cm); regChange(cm); clearCaches(cm); - setTimeout(function(){updateScrollbars(cm.display, cm.doc.height);}, 100); + setTimeout(function(){updateScrollbars(cm);}, 100); } function estimateHeight(cm) { @@ -241,9 +239,10 @@ window.CodeMirror = (function() { } function keyMapChanged(cm) { - var style = keyMap[cm.options.keyMap].style; + var map = keyMap[cm.options.keyMap], style = map.style; cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + (style ? " cm-keymap-" + style : ""); + cm.state.disableInput = map.disableInput; } function themeChanged(cm) { @@ -255,6 +254,7 @@ window.CodeMirror = (function() { function guttersChanged(cm) { updateGutters(cm); regChange(cm); + setTimeout(function(){alignHorizontally(cm);}, 20); } function updateGutters(cm) { @@ -321,28 +321,41 @@ window.CodeMirror = (function() { // Re-synchronize the fake scrollbars with the actual size of the // content. Optionally force a scrollTop. - function updateScrollbars(d /* display */, docHeight) { + function updateScrollbars(cm) { + var d = cm.display, docHeight = cm.doc.height; var totalHeight = docHeight + paddingVert(d); d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px"; + d.gutters.style.height = Math.max(totalHeight, d.scroller.clientHeight - scrollerCutOff) + "px"; var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight); - var needsH = d.scroller.scrollWidth > d.scroller.clientWidth; - var needsV = scrollHeight > d.scroller.clientHeight; + var needsH = d.scroller.scrollWidth > (d.scroller.clientWidth + 1); + var needsV = scrollHeight > (d.scroller.clientHeight + 1); if (needsV) { d.scrollbarV.style.display = "block"; d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0"; d.scrollbarV.firstChild.style.height = (scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + "px"; - } else d.scrollbarV.style.display = ""; + } else { + d.scrollbarV.style.display = ""; + d.scrollbarV.firstChild.style.height = "0"; + } if (needsH) { d.scrollbarH.style.display = "block"; d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0"; d.scrollbarH.firstChild.style.width = (d.scroller.scrollWidth - d.scroller.clientWidth + d.scrollbarH.clientWidth) + "px"; - } else d.scrollbarH.style.display = ""; + } else { + d.scrollbarH.style.display = ""; + d.scrollbarH.firstChild.style.width = "0"; + } if (needsH && needsV) { d.scrollbarFiller.style.display = "block"; d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px"; } else d.scrollbarFiller.style.display = ""; + if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { + d.gutterFiller.style.display = "block"; + d.gutterFiller.style.height = scrollbarWidth(d.measure) + "px"; + d.gutterFiller.style.width = d.gutters.offsetWidth + "px"; + } else d.gutterFiller.style.display = ""; if (mac_geLion && scrollbarWidth(d.measure) === 0) d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px"; @@ -397,24 +410,38 @@ window.CodeMirror = (function() { // DISPLAY DRAWING - function updateDisplay(cm, changes, viewPort) { - var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo; - var updated = updateDisplayInner(cm, changes, viewPort); + function updateDisplay(cm, changes, viewPort, forced) { + var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo, updated; + var visible = visibleLines(cm.display, cm.doc, viewPort); + for (;;) { + if (!updateDisplayInner(cm, changes, visible, forced)) break; + forced = false; + updated = true; + updateSelection(cm); + updateScrollbars(cm); + + // Clip forced viewport to actual scrollable area + if (viewPort) + viewPort = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, + typeof viewPort == "number" ? viewPort : viewPort.top); + visible = visibleLines(cm.display, cm.doc, viewPort); + if (visible.from >= cm.display.showingFrom && visible.to <= cm.display.showingTo) + break; + changes = []; + } + if (updated) { signalLater(cm, "update", cm); if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo) signalLater(cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo); } - updateSelection(cm); - updateScrollbars(cm.display, cm.doc.height); - return updated; } // Uses a set of changes plus the current scroll position to // determine which DOM updates have to be made, and makes the // updates. - function updateDisplayInner(cm, changes, viewPort) { + function updateDisplayInner(cm, changes, visible, forced) { var display = cm.display, doc = cm.doc; if (!display.wrapper.clientWidth) { display.showingFrom = display.showingTo = doc.first; @@ -422,12 +449,8 @@ window.CodeMirror = (function() { return; } - // Compute the new visible window - // If scrollTop is specified, use that to determine which lines - // to render instead of the current scrollbar position. - var visible = visibleLines(display, doc, viewPort); // Bail out if the visible area is already rendered and nothing changed. - if (changes.length == 0 && + if (!forced && changes.length == 0 && visible.from > display.showingFrom && visible.to < display.showingTo) return; @@ -440,7 +463,7 @@ window.CodeMirror = (function() { var positionsChangedFrom = Infinity; if (cm.options.lineNumbers) for (var i = 0; i < changes.length; ++i) - if (changes[i].diff) { positionsChangedFrom = changes[i].from; break; } + if (changes[i].diff && changes[i].from < positionsChangedFrom) { positionsChangedFrom = changes[i].from; } var end = doc.first + doc.size; var from = Math.max(visible.from - cm.options.viewportMargin, doc.first); @@ -480,26 +503,39 @@ window.CodeMirror = (function() { if (range.from >= range.to) intact.splice(i--, 1); else intactLines += range.to - range.from; } - if (intactLines == to - from && from == display.showingFrom && to == display.showingTo) { + if (!forced && intactLines == to - from && from == display.showingFrom && to == display.showingTo) { updateViewOffset(cm); return; } intact.sort(function(a, b) {return a.from - b.from;}); - var focused = document.activeElement; + // Avoid crashing on IE's "unspecified error" when in iframes + try { + var focused = document.activeElement; + } catch(e) {} if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none"; patchDisplay(cm, from, to, intact, positionsChangedFrom); display.lineDiv.style.display = ""; - if (document.activeElement != focused && focused.offsetHeight) focused.focus(); + if (focused && document.activeElement != focused && focused.offsetHeight) focused.focus(); var different = from != display.showingFrom || to != display.showingTo || display.lastSizeC != display.wrapper.clientHeight; // This is just a bogus formula that detects when the editor is // resized or the font size changes. - if (different) display.lastSizeC = display.wrapper.clientHeight; + if (different) { + display.lastSizeC = display.wrapper.clientHeight; + startWorker(cm, 400); + } display.showingFrom = from; display.showingTo = to; - startWorker(cm, 100); + updateHeightsInViewport(cm); + updateViewOffset(cm); + + return true; + } + + function updateHeightsInViewport(cm) { + var display = cm.display; var prevBottom = display.lineDiv.offsetTop; for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) { if (ie_lt8) { @@ -519,11 +555,6 @@ window.CodeMirror = (function() { widgets[i].height = widgets[i].node.offsetHeight; } } - updateViewOffset(cm); - - if (visibleLines(display, doc, viewPort).to > to) - updateDisplayInner(cm, [], viewPort); - return true; } function updateViewOffset(cm) { @@ -589,8 +620,9 @@ window.CodeMirror = (function() { if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift(); if (lineIsHidden(cm.doc, line)) { if (line.height != 0) updateLineHeight(line, 0); - if (line.widgets && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) - if (line.widgets[i].showIfHidden) { + if (line.widgets && cur && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) { + var w = line.widgets[i]; + if (w.showIfHidden) { var prev = cur.previousSibling; if (/pre/i.test(prev.nodeName)) { var wrap = elt("div", null, null, "position: relative"); @@ -598,9 +630,11 @@ window.CodeMirror = (function() { wrap.appendChild(prev); prev = wrap; } - var wnode = prev.appendChild(elt("div", [line.widgets[i].node], "CodeMirror-linewidget")); - positionLineWidget(line.widgets[i], wnode, prev, dims); + var wnode = prev.appendChild(elt("div", [w.node], "CodeMirror-linewidget")); + if (!w.handleMouseEvents) wnode.ignoreEvents = true; + positionLineWidget(w, wnode, prev, dims); } + } } else if (nextIntact && nextIntact.from <= lineN && nextIntact.to > lineN) { // This line is intact. Skip to the actual node. Update its // line number if needed. @@ -643,25 +677,25 @@ window.CodeMirror = (function() { if (reuse) { reuse.alignable = null; - var isOk = true, widgetsSeen = 0; + var isOk = true, widgetsSeen = 0, insertBefore = null; for (var n = reuse.firstChild, next; n; n = next) { next = n.nextSibling; if (!/\bCodeMirror-linewidget\b/.test(n.className)) { reuse.removeChild(n); } else { - for (var i = 0, first = true; i < line.widgets.length; ++i) { - var widget = line.widgets[i], isFirst = false; - if (!widget.above) { isFirst = first; first = false; } + for (var i = 0; i < line.widgets.length; ++i) { + var widget = line.widgets[i]; if (widget.node == n.firstChild) { + if (!widget.above && !insertBefore) insertBefore = n; positionLineWidget(widget, n, reuse, dims); ++widgetsSeen; - if (isFirst) reuse.insertBefore(lineElement, n); break; } } if (i == line.widgets.length) { isOk = false; break; } } } + reuse.insertBefore(lineElement, insertBefore); if (isOk && widgetsSeen == line.widgets.length) { wrap = reuse; reuse.className = line.wrapClass || ""; @@ -696,6 +730,7 @@ window.CodeMirror = (function() { if (ie_lt8) wrap.style.zIndex = 2; if (line.widgets && wrap != reuse) for (var i = 0, ws = line.widgets; i < ws.length; ++i) { var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); + if (!widget.handleMouseEvents) node.ignoreEvents = true; positionLineWidget(widget, node, wrap, dims); if (widget.above) wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement); @@ -739,12 +774,14 @@ window.CodeMirror = (function() { display.selectionDiv.style.display = "none"; // Move the hidden textarea near the cursor to prevent scrolling artifacts - var headPos = cursorCoords(cm, cm.doc.sel.head, "div"); - var wrapOff = getRect(display.wrapper), lineOff = getRect(display.lineDiv); - display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10, - headPos.top + lineOff.top - wrapOff.top)) + "px"; - display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10, - headPos.left + lineOff.left - wrapOff.left)) + "px"; + if (cm.options.moveInputWithCursor) { + var headPos = cursorCoords(cm, cm.doc.sel.head, "div"); + var wrapOff = getRect(display.wrapper), lineOff = getRect(display.lineDiv); + display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10, + headPos.top + lineOff.top - wrapOff.top)) + "px"; + display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10, + headPos.left + lineOff.left - wrapOff.left)) + "px"; + } } // No selection, plain cursor @@ -776,67 +813,59 @@ window.CodeMirror = (function() { "px; height: " + (bottom - top) + "px")); } - function drawForLine(line, fromArg, toArg, retTop) { + function drawForLine(line, fromArg, toArg) { var lineObj = getLine(doc, line); - var lineLen = lineObj.text.length, rVal = retTop ? Infinity : -Infinity; - function coords(ch) { - return charCoords(cm, Pos(line, ch), "div", lineObj); + var lineLen = lineObj.text.length; + var start, end; + function coords(ch, bias) { + return charCoords(cm, Pos(line, ch), "div", lineObj, bias); } iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) { - var leftPos = coords(dir == "rtl" ? to - 1 : from); - var rightPos = coords(dir == "rtl" ? from : to - 1); - var left = leftPos.left, right = rightPos.right; + var leftPos = coords(from, "left"), rightPos, left, right; + if (from == to) { + rightPos = leftPos; + left = right = leftPos.left; + } else { + rightPos = coords(to - 1, "right"); + if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; } + left = leftPos.left; + right = rightPos.right; + } + if (fromArg == null && from == 0) left = pl; if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part add(left, leftPos.top, null, leftPos.bottom); left = pl; if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top); } if (toArg == null && to == lineLen) right = clientWidth; - if (fromArg == null && from == 0) left = pl; - rVal = retTop ? Math.min(rightPos.top, rVal) : Math.max(rightPos.bottom, rVal); + if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left) + start = leftPos; + if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right) + end = rightPos; if (left < pl + 1) left = pl; add(left, rightPos.top, right - left, rightPos.bottom); }); - return rVal; + return {start: start, end: end}; } if (sel.from.line == sel.to.line) { drawForLine(sel.from.line, sel.from.ch, sel.to.ch); } else { - var fromObj = getLine(doc, sel.from.line); - var cur = fromObj, merged, path = [sel.from.line, sel.from.ch], singleLine; - while (merged = collapsedSpanAtEnd(cur)) { - var found = merged.find(); - path.push(found.from.ch, found.to.line, found.to.ch); - if (found.to.line == sel.to.line) { - path.push(sel.to.ch); - singleLine = true; - break; + var fromLine = getLine(doc, sel.from.line), toLine = getLine(doc, sel.to.line); + var singleVLine = visualLine(doc, fromLine) == visualLine(doc, toLine); + var leftEnd = drawForLine(sel.from.line, sel.from.ch, singleVLine ? fromLine.text.length : null).end; + var rightStart = drawForLine(sel.to.line, singleVLine ? 0 : null, sel.to.ch).start; + if (singleVLine) { + if (leftEnd.top < rightStart.top - 2) { + add(leftEnd.right, leftEnd.top, null, leftEnd.bottom); + add(pl, rightStart.top, rightStart.left, rightStart.bottom); + } else { + add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom); } - cur = getLine(doc, found.to.line); - } - - // This is a single, merged line - if (singleLine) { - for (var i = 0; i < path.length; i += 3) - drawForLine(path[i], path[i+1], path[i+2]); - } else { - var middleTop, middleBot, toObj = getLine(doc, sel.to.line); - if (sel.from.ch) - // Draw the first line of selection. - middleTop = drawForLine(sel.from.line, sel.from.ch, null, false); - else - // Simply include it in the middle block. - middleTop = heightAtLine(cm, fromObj) - display.viewOffset; - - if (!sel.to.ch) - middleBot = heightAtLine(cm, toObj) - display.viewOffset; - else - middleBot = drawForLine(sel.to.line, collapsedSpanAtStart(toObj) ? null : 0, sel.to.ch, true); - - if (middleTop < middleBot) add(pl, middleTop, null, middleBot); } + if (leftEnd.bottom < rightStart.top) + add(pl, leftEnd.bottom, null, rightStart.top); } removeChildrenAndAdd(display.selectionDiv, fragment); @@ -845,14 +874,15 @@ window.CodeMirror = (function() { // Cursor-blinking function restartBlink(cm) { + if (!cm.state.focused) return; var display = cm.display; clearInterval(display.blinker); var on = true; display.cursor.style.visibility = display.otherCursor.style.visibility = ""; - display.blinker = setInterval(function() { - if (!display.cursor.offsetHeight) return; - display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden"; - }, cm.options.cursorBlinkRate); + if (cm.options.cursorBlinkRate > 0) + display.blinker = setInterval(function() { + display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden"; + }, cm.options.cursorBlinkRate); } // HIGHLIGHT WORKER @@ -902,12 +932,12 @@ window.CodeMirror = (function() { // valid state. If that fails, it returns the line with the // smallest indentation, which tends to need the least context to // parse correctly. - function findStartLine(cm, n) { - var minindent, minline, doc = cm.doc; - for (var search = n, lim = n - 100; search > lim; --search) { + function findStartLine(cm, n, precise) { + var minindent, minline, doc = cm.doc, maxScan = cm.doc.mode.innerMode ? 1000 : 100; + for (var search = n, lim = n - maxScan; search > lim; --search) { if (search <= doc.first) return doc.first; var line = getLine(doc, search - 1); - if (line.stateAfter) return search; + if (line.stateAfter && (!precise || search <= doc.frontier)) return search; var indented = countColumn(line.text, null, cm.options.tabSize); if (minline == null || minindent > indented) { minline = search - 1; @@ -917,10 +947,10 @@ window.CodeMirror = (function() { return minline; } - function getStateBefore(cm, n) { + function getStateBefore(cm, n, precise) { var doc = cm.doc, display = cm.display; - if (!doc.mode.startState) return true; - var pos = findStartLine(cm, n), state = pos > doc.first && getLine(doc, pos-1).stateAfter; + if (!doc.mode.startState) return true; + var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter; if (!state) state = startState(doc.mode); else state = copyState(doc.mode, state); doc.iter(pos, n, function(line) { @@ -941,7 +971,7 @@ window.CodeMirror = (function() { return e.offsetLeft; } - function measureChar(cm, line, ch, data) { + function measureChar(cm, line, ch, data, bias) { var dir = -1; data = data || measureLine(cm, line); @@ -950,9 +980,13 @@ window.CodeMirror = (function() { if (r) break; if (dir < 0 && pos == 0) dir = 1; } + bias = pos > ch ? "left" : pos < ch ? "right" : bias; + if (bias == "left" && r.leftSide) r = r.leftSide; + else if (bias == "right" && r.rightSide) r = r.rightSide; return {left: pos < ch ? r.right : r.left, right: pos > ch ? r.left : r.right, - top: r.top, bottom: r.bottom}; + top: r.top, + bottom: r.bottom}; } function findCachedMeasurement(cm, line) { @@ -962,29 +996,34 @@ window.CodeMirror = (function() { if (memo.text == line.text && memo.markedSpans == line.markedSpans && cm.display.scroller.clientWidth == memo.width && memo.classes == line.textClass + "|" + line.bgClass + "|" + line.wrapClass) - return memo.measure; + return memo; } } + function clearCachedMeasurement(cm, line) { + var exists = findCachedMeasurement(cm, line); + if (exists) exists.text = exists.measure = exists.markedSpans = null; + } + function measureLine(cm, line) { // First look in the cache - var measure = findCachedMeasurement(cm, line); - if (!measure) { - // Failing that, recompute and store result in cache - measure = measureLineInner(cm, line); - var cache = cm.display.measureLineCache; - var memo = {text: line.text, width: cm.display.scroller.clientWidth, - markedSpans: line.markedSpans, measure: measure, - classes: line.textClass + "|" + line.bgClass + "|" + line.wrapClass}; - if (cache.length == 16) cache[++cm.display.measureLineCachePos % 16] = memo; - else cache.push(memo); - } + var cached = findCachedMeasurement(cm, line); + if (cached) return cached.measure; + + // Failing that, recompute and store result in cache + var measure = measureLineInner(cm, line); + var cache = cm.display.measureLineCache; + var memo = {text: line.text, width: cm.display.scroller.clientWidth, + markedSpans: line.markedSpans, measure: measure, + classes: line.textClass + "|" + line.bgClass + "|" + line.wrapClass}; + if (cache.length == 16) cache[++cm.display.measureLineCachePos % 16] = memo; + else cache.push(memo); return measure; } function measureLineInner(cm, line) { var display = cm.display, measure = emptyArray(line.text.length); - var pre = lineContent(cm, line, measure); + var pre = lineContent(cm, line, measure, true); // IE does not cache element positions of inline elements between // calls to getBoundingClientRect. This makes the loop below, @@ -1020,30 +1059,52 @@ window.CodeMirror = (function() { if (ie_lt9 && display.measure.first != pre) removeChildrenAndAdd(display.measure, pre); - for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) { - var size = getRect(cur); - var top = Math.max(0, size.top - outer.top), bot = Math.min(size.bottom - outer.top, maxBot); - for (var j = 0; j < vranges.length; j += 2) { - var rtop = vranges[j], rbot = vranges[j+1]; + function measureRect(rect) { + var top = rect.top - outer.top, bot = rect.bottom - outer.top; + if (bot > maxBot) bot = maxBot; + if (top < 0) top = 0; + for (var i = vranges.length - 2; i >= 0; i -= 2) { + var rtop = vranges[i], rbot = vranges[i+1]; if (rtop > bot || rbot < top) continue; if (rtop <= top && rbot >= bot || top <= rtop && bot >= rbot || Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) { - vranges[j] = Math.min(top, rtop); - vranges[j+1] = Math.max(bot, rbot); + vranges[i] = Math.min(top, rtop); + vranges[i+1] = Math.max(bot, rbot); break; } } - if (j == vranges.length) vranges.push(top, bot); - var right = size.right; - if (cur.measureRight) right = getRect(cur.measureRight).left; - data[i] = {left: size.left - outer.left, right: right - outer.left, top: j}; + if (i < 0) { i = vranges.length; vranges.push(top, bot); } + return {left: rect.left - outer.left, + right: rect.right - outer.left, + top: i, bottom: null}; } - for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) { - var vr = cur.top; - cur.top = vranges[vr]; cur.bottom = vranges[vr+1]; + function finishRect(rect) { + rect.bottom = vranges[rect.top+1]; + rect.top = vranges[rect.top]; } + for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) { + var node = cur, rect = null; + // A widget might wrap, needs special care + if (/\bCodeMirror-widget\b/.test(cur.className) && cur.getClientRects) { + if (cur.firstChild.nodeType == 1) node = cur.firstChild; + var rects = node.getClientRects(); + if (rects.length > 1) { + rect = data[i] = measureRect(rects[0]); + rect.rightSide = measureRect(rects[rects.length - 1]); + } + } + if (!rect) rect = data[i] = measureRect(getRect(node)); + if (cur.measureRight) rect.right = getRect(cur.measureRight).left; + if (cur.leftSide) rect.leftSide = measureRect(getRect(cur.leftSide)); + } + removeChildren(cm.display.measure); + for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) { + finishRect(cur); + if (cur.leftSide) finishRect(cur.leftSide); + if (cur.rightSide) finishRect(cur.rightSide); + } return data; } @@ -1054,9 +1115,9 @@ window.CodeMirror = (function() { if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true; } var cached = !hasBadSpan && findCachedMeasurement(cm, line); - if (cached) return measureChar(cm, line, line.text.length, cached).right; + if (cached) return measureChar(cm, line, line.text.length, cached.measure, "right").right; - var pre = lineContent(cm, line); + var pre = lineContent(cm, line, null, true); var end = pre.appendChild(zeroWidthElement(cm.display.measure)); removeChildrenAndAdd(cm.display.measure, pre); return getRect(end).right - getRect(cm.display.lineDiv).left; @@ -1065,10 +1126,13 @@ window.CodeMirror = (function() { function clearCaches(cm) { cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0; cm.display.cachedCharWidth = cm.display.cachedTextHeight = null; - cm.display.maxLineChanged = true; + if (!cm.options.lineWrapping) cm.display.maxLineChanged = true; cm.display.lineNumChars = null; } + function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; } + function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; } + // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page" function intoCoordSystem(cm, lineObj, rect, context) { if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) { @@ -1078,11 +1142,12 @@ window.CodeMirror = (function() { if (context == "line") return rect; if (!context) context = "local"; var yOff = heightAtLine(cm, lineObj); - if (context != "local") yOff -= cm.display.viewOffset; - if (context == "page") { + if (context == "local") yOff += paddingTop(cm.display); + else yOff -= cm.display.viewOffset; + if (context == "page" || context == "window") { var lOff = getRect(cm.display.lineSpace); - yOff += lOff.top + (window.pageYOffset || (document.documentElement || document.body).scrollTop); - var xOff = lOff.left + (window.pageXOffset || (document.documentElement || document.body).scrollLeft); + yOff += lOff.top + (context == "window" ? 0 : pageScrollY()); + var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()); rect.left += xOff; rect.right += xOff; } rect.top += yOff; rect.bottom += yOff; @@ -1090,68 +1155,62 @@ window.CodeMirror = (function() { } // Context may be "window", "page", "div", or "local"/null - // Result is in local coords + // Result is in "div" coords function fromCoordSystem(cm, coords, context) { if (context == "div") return coords; var left = coords.left, top = coords.top; + // First move into "page" coordinate system if (context == "page") { - left -= window.pageXOffset || (document.documentElement || document.body).scrollLeft; - top -= window.pageYOffset || (document.documentElement || document.body).scrollTop; + left -= pageScrollX(); + top -= pageScrollY(); + } else if (context == "local" || !context) { + var localBox = getRect(cm.display.sizer); + left += localBox.left; + top += localBox.top; } + var lineSpaceBox = getRect(cm.display.lineSpace); - left -= lineSpaceBox.left; - top -= lineSpaceBox.top; - if (context == "local" || !context) { - var editorBox = getRect(cm.display.wrapper); - left -= editorBox.left; - top -= editorBox.top; - } - return {left: left, top: top}; + return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}; } - function charCoords(cm, pos, context, lineObj) { + function charCoords(cm, pos, context, lineObj, bias) { if (!lineObj) lineObj = getLine(cm.doc, pos.line); - return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch), context); + return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, null, bias), context); } function cursorCoords(cm, pos, context, lineObj, measurement) { lineObj = lineObj || getLine(cm.doc, pos.line); if (!measurement) measurement = measureLine(cm, lineObj); function get(ch, right) { - var m = measureChar(cm, lineObj, ch, measurement); + var m = measureChar(cm, lineObj, ch, measurement, right ? "right" : "left"); if (right) m.left = m.right; else m.right = m.left; return intoCoordSystem(cm, lineObj, m, context); } + function getBidi(ch, partPos) { + var part = order[partPos], right = part.level % 2; + if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) { + part = order[--partPos]; + ch = bidiRight(part) - (part.level % 2 ? 0 : 1); + right = true; + } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) { + part = order[++partPos]; + ch = bidiLeft(part) - part.level % 2; + right = false; + } + if (right && ch == part.to && ch > part.from) return get(ch - 1); + return get(ch, right); + } var order = getOrder(lineObj), ch = pos.ch; if (!order) return get(ch); - var main, other, linedir = order[0].level; - for (var i = 0; i < order.length; ++i) { - var part = order[i], rtl = part.level % 2, nb, here; - if (part.from < ch && part.to > ch) return get(ch, rtl); - var left = rtl ? part.to : part.from, right = rtl ? part.from : part.to; - if (left == ch) { - // IE returns bogus offsets and widths for edges where the - // direction flips, but only for the side with the lower - // level. So we try to use the side with the higher level. - if (i && part.level < (nb = order[i-1]).level) here = get(nb.level % 2 ? nb.from : nb.to - 1, true); - else here = get(rtl && part.from != part.to ? ch - 1 : ch); - if (rtl == linedir) main = here; else other = here; - } else if (right == ch) { - var nb = i < order.length - 1 && order[i+1]; - if (!rtl && nb && nb.from == nb.to) continue; - if (nb && part.level < nb.level) here = get(nb.level % 2 ? nb.to - 1 : nb.from); - else here = get(rtl ? ch : ch - 1, true); - if (rtl == linedir) main = here; else other = here; - } - } - if (linedir && !ch) other = get(order[0].to - 1); - if (!main) return other; - if (other) main.other = other; - return main; + var partPos = getBidiPartAt(order, ch); + var val = getBidi(ch, partPos); + if (bidiOther != null) val.other = getBidi(ch, bidiOther); + return val; } - function PosMaybeOutside(line, ch, outside) { + function PosWithInfo(line, ch, outside, xRel) { var pos = new Pos(line, ch); + pos.xRel = xRel; if (outside) pos.outside = true; return pos; } @@ -1160,10 +1219,10 @@ window.CodeMirror = (function() { function coordsChar(cm, x, y) { var doc = cm.doc; y += cm.display.viewOffset; - if (y < 0) return PosMaybeOutside(doc.first, 0, true); + if (y < 0) return PosWithInfo(doc.first, 0, true, -1); var lineNo = lineAtHeight(doc, y), last = doc.first + doc.size - 1; if (lineNo > last) - return PosMaybeOutside(doc.first + doc.size - 1, getLine(doc, last).text.length, true); + return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1); if (x < 0) x = 0; for (;;) { @@ -1171,7 +1230,7 @@ window.CodeMirror = (function() { var found = coordsCharInner(cm, lineObj, lineNo, x, y); var merged = collapsedSpanAtEnd(lineObj); var mergedPos = merged && merged.find(); - if (merged && found.ch >= mergedPos.from.ch) + if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0)) lineNo = mergedPos.to.line; else return found; @@ -1197,14 +1256,15 @@ window.CodeMirror = (function() { var from = lineLeft(lineObj), to = lineRight(lineObj); var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine; - if (x > toX) return PosMaybeOutside(lineNo, to, toOutside); + if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1); // Do a binary search between these bounds. for (;;) { if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { - var after = x - fromX < toX - x, ch = after ? from : to; + var ch = x < fromX || x - fromX <= toX - x ? from : to; + var xDiff = x - (ch == from ? fromX : toX); while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch; - var pos = PosMaybeOutside(lineNo, ch, after ? fromOutside : toOutside); - pos.after = after; + var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside, + xDiff < 0 ? -1 : xDiff ? 1 : 0); return pos; } var step = Math.ceil(dist / 2), middle = from + step; @@ -1213,8 +1273,8 @@ window.CodeMirror = (function() { for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1); } var middleX = getX(middle); - if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist -= step;} - else {from = middle; fromX = middleX; fromOutside = wrongLine; dist = step;} + if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;} + else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;} } } @@ -1261,10 +1321,12 @@ window.CodeMirror = (function() { // An array of ranges of lines that have to be updated. See // updateDisplay. changes: [], + forceUpdate: false, updateInput: null, userSelChange: null, textChanged: null, selectionChanged: false, + cursorActivity: false, updateMaxLine: false, updateScrollPos: false, id: ++nextOpId @@ -1277,7 +1339,7 @@ window.CodeMirror = (function() { cm.curOp = null; if (op.updateMaxLine) computeMaxLength(cm); - if (display.maxLineChanged && !cm.options.lineWrapping) { + if (display.maxLineChanged && !cm.options.lineWrapping && display.maxLine) { var width = measureLineWidth(cm, display.maxLine); display.sizer.style.minWidth = Math.max(0, width + 3 + scrollerCutOff) + "px"; display.maxLineChanged = false; @@ -1292,8 +1354,8 @@ window.CodeMirror = (function() { var coords = cursorCoords(cm, doc.sel.head); newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom); } - if (op.changes.length || newScrollPos && newScrollPos.scrollTop != null) { - updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop); + if (op.changes.length || op.forceUpdate || newScrollPos && newScrollPos.scrollTop != null) { + updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop, op.forceUpdate); if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop; } if (!updated && op.selectionChanged) updateSelection(cm); @@ -1301,6 +1363,8 @@ window.CodeMirror = (function() { display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = newScrollPos.scrollTop; display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = newScrollPos.scrollLeft; alignHorizontally(cm); + if (op.scrollToPos) + scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos), op.scrollToPosMargin); } else if (newScrollPos) { scrollCursorIntoView(cm); } @@ -1322,7 +1386,7 @@ window.CodeMirror = (function() { } if (op.textChanged) signal(cm, "change", cm, op.textChanged); - if (op.selectionChanged) signal(cm, "cursorActivity", cm); + if (op.cursorActivity) signal(cm, "cursorActivity", cm); if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i](); } @@ -1387,31 +1451,36 @@ window.CodeMirror = (function() { // supported or compatible enough yet to rely on.) function readInput(cm) { var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel; - if (!cm.state.focused || hasSelection(input) || isReadOnly(cm)) return false; + if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.state.disableInput) return false; + if (cm.state.pasteIncoming && cm.state.fakedLastChar) { + input.value = input.value.substring(0, input.value.length - 1); + cm.state.fakedLastChar = false; + } var text = input.value; if (text == prevInput && posEq(sel.from, sel.to)) return false; - // IE enjoys randomly deselecting our input's text when - // re-focusing. If the selection is gone but the cursor is at the - // start of the input, that's probably what happened. - if (ie && text && input.selectionStart === 0) { + if (ie && !ie_lt9 && cm.display.inputHasSelection === text) { resetInput(cm, true); return false; } + var withOp = !cm.curOp; if (withOp) startOperation(cm); sel.shift = false; var same = 0, l = Math.min(prevInput.length, text.length); - while (same < l && prevInput[same] == text[same]) ++same; + while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same; var from = sel.from, to = sel.to; if (same < prevInput.length) from = Pos(from.line, from.ch - (prevInput.length - same)); else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming) to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + (text.length - same))); - var updateInput = cm.curOp.updateInput; - makeChange(cm.doc, {from: from, to: to, text: splitLines(text.slice(same)), - origin: cm.state.pasteIncoming ? "paste" : "+input"}, "end"); + var updateInput = cm.curOp.updateInput; + var changeEvent = {from: from, to: to, text: splitLines(text.slice(same)), + origin: cm.state.pasteIncoming ? "paste" : "+input"}; + makeChange(cm.doc, changeEvent, "end"); cm.curOp.updateInput = updateInput; + signalLater(cm, "inputRead", cm, changeEvent); + if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = ""; else cm.display.prevInput = text; if (withOp) endOperation(cm); @@ -1425,10 +1494,14 @@ window.CodeMirror = (function() { cm.display.prevInput = ""; minimal = hasCopyEvent && (doc.sel.to.line - doc.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000); - if (minimal) cm.display.input.value = "-"; - else cm.display.input.value = selected || cm.getSelection(); + var content = minimal ? "-" : selected || cm.getSelection(); + cm.display.input.value = content; if (cm.state.focused) selectInput(cm.display.input); - } else if (user) cm.display.prevInput = cm.display.input.value = ""; + if (ie && !ie_lt9) cm.display.inputHasSelection = content; + } else if (user) { + cm.display.prevInput = cm.display.input.value = ""; + if (ie && !ie_lt9) cm.display.inputHasSelection = null; + } cm.display.inaccurateSelection = minimal; } @@ -1446,7 +1519,17 @@ window.CodeMirror = (function() { function registerEventHandlers(cm) { var d = cm.display; on(d.scroller, "mousedown", operation(cm, onMouseDown)); - on(d.scroller, "dblclick", operation(cm, e_preventDefault)); + if (ie) + on(d.scroller, "dblclick", operation(cm, function(e) { + if (signalDOMEvent(cm, e)) return; + var pos = posFromMouse(cm, e); + if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return; + e_preventDefault(e); + var word = findWordAt(getLine(cm.doc, pos.line).text, pos); + extendSelection(cm.doc, word.from, word.to); + })); + else + on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); }); on(d.lineSpace, "selectstart", function(e) { if (!eventInWidget(d, e)) e_preventDefault(e); }); @@ -1478,11 +1561,15 @@ window.CodeMirror = (function() { // Prevent wrapper from ever scrolling on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); + var resizeTimer; function onResize() { - // Might be a text scaling operation, clear size caches. - d.cachedCharWidth = d.cachedTextHeight = null; - clearCaches(cm); - runInOp(cm, bind(regChange, cm)); + if (resizeTimer == null) resizeTimer = setTimeout(function() { + resizeTimer = null; + // Might be a text scaling operation, clear size caches. + d.cachedCharWidth = d.cachedTextHeight = knownScrollbarWidth = null; + clearCaches(cm); + runInOp(cm, bind(regChange, cm)); + }, 100); } on(window, "resize", onResize); // Above handler holds on to the editor and its data structures. @@ -1496,7 +1583,7 @@ window.CodeMirror = (function() { setTimeout(unregister, 5000); on(d.input, "keyup", operation(cm, function(e) { - if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; + if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; if (e.keyCode == 16) cm.doc.sel.shift = false; })); on(d.input, "input", bind(fastPoll, cm)); @@ -1506,7 +1593,7 @@ window.CodeMirror = (function() { on(d.input, "blur", bind(onBlur, cm)); function drag_(e) { - if (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return; + if (signalDOMEvent(cm, e) || cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return; e_stop(e); } if (cm.options.dragDrop) { @@ -1515,12 +1602,22 @@ window.CodeMirror = (function() { on(d.scroller, "dragover", drag_); on(d.scroller, "drop", operation(cm, onDrop)); } - on(d.scroller, "paste", function(e){ + on(d.scroller, "paste", function(e) { if (eventInWidget(d, e)) return; focusInput(cm); fastPoll(cm); }); on(d.input, "paste", function() { + // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206 + // Add a char to the end of textarea before paste occur so that + // selection doesn't span to the end of textarea. + if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) { + var start = d.input.selectionStart, end = d.input.selectionEnd; + d.input.value += "$"; + d.input.selectionStart = start; + d.input.selectionEnd = end; + cm.state.fakedLastChar = true; + } cm.state.pasteIncoming = true; fastPoll(cm); }); @@ -1545,9 +1642,7 @@ window.CodeMirror = (function() { function eventInWidget(display, e) { for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { - if (!n) return true; - if (/\bCodeMirror-(?:line)?widget\b/.test(n.className) || - n.parentNode == display.sizer && n != display.mover) return true; + if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true; } } @@ -1557,7 +1652,7 @@ window.CodeMirror = (function() { var target = e_target(e); if (target == display.scrollbarH || target == display.scrollbarH.firstChild || target == display.scrollbarV || target == display.scrollbarV.firstChild || - target == display.scrollbarFiller) return null; + target == display.scrollbarFiller || target == display.gutterFiller) return null; } var x, y, space = getRect(display.lineSpace); // Fails unpredictably on IE[67] when mouse is dragged around quickly. @@ -1567,6 +1662,7 @@ window.CodeMirror = (function() { var lastClick, lastDoubleClick; function onMouseDown(e) { + if (signalDOMEvent(this, e)) return; var cm = this, display = cm.display, doc = cm.doc, sel = doc.sel; sel.shift = e.shiftKey; @@ -1585,6 +1681,7 @@ window.CodeMirror = (function() { if (captureMiddleClick) onContextMenu.call(cm, cm, e); return; case 2: + if (webkit) cm.state.lastMiddleDown = +new Date; if (start) extendSelection(cm.doc, start); setTimeout(bind(focusInput, cm), 20); e_preventDefault(e); @@ -1637,9 +1734,12 @@ window.CodeMirror = (function() { e_preventDefault(e); if (type == "single") extendSelection(cm.doc, clipPos(doc, start)); - var startstart = sel.from, startend = sel.to; + var startstart = sel.from, startend = sel.to, lastPos = start; function doSelect(cur) { + if (posEq(lastPos, cur)) return; + lastPos = cur; + if (type == "single") { extendSelection(cm.doc, clipPos(doc, start), cur); return; @@ -1687,8 +1787,6 @@ window.CodeMirror = (function() { function done(e) { counter = Infinity; - var cur = posFromMouse(cm, e); - if (cur) doSelect(cur); e_preventDefault(e); focusInput(cm); off(document, "mousemove", move); @@ -1704,11 +1802,41 @@ window.CodeMirror = (function() { on(document, "mouseup", up); } + function clickInGutter(cm, e) { + var display = cm.display; + try { var mX = e.clientX, mY = e.clientY; } + catch(e) { return false; } + + if (mX >= Math.floor(getRect(display.gutters).right)) return false; + e_preventDefault(e); + if (!hasHandler(cm, "gutterClick")) return true; + + var lineBox = getRect(display.lineDiv); + if (mY > lineBox.bottom) return true; + mY -= lineBox.top - display.viewOffset; + + for (var i = 0; i < cm.options.gutters.length; ++i) { + var g = display.gutters.childNodes[i]; + if (g && getRect(g).right >= mX) { + var line = lineAtHeight(cm.doc, mY); + var gutter = cm.options.gutters[i]; + signalLater(cm, "gutterClick", cm, line, gutter, e); + break; + } + } + return true; + } + + // Kludge to work around strange IE behavior where it'll sometimes + // re-fire a series of drag-related events right after the drop (#1551) + var lastDrop = 0; + function onDrop(e) { var cm = this; - if (eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e)))) + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e)))) return; e_preventDefault(e); + if (ie) lastDrop = +new Date; var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; if (!pos || isReadOnly(cm)) return; if (files && files.length && window.FileReader && window.File) { @@ -1748,40 +1876,16 @@ window.CodeMirror = (function() { } } - function clickInGutter(cm, e) { - var display = cm.display; - try { var mX = e.clientX, mY = e.clientY; } - catch(e) { return false; } - - if (mX >= Math.floor(getRect(display.gutters).right)) return false; - e_preventDefault(e); - if (!hasHandler(cm, "gutterClick")) return true; - - var lineBox = getRect(display.lineDiv); - if (mY > lineBox.bottom) return true; - mY -= lineBox.top - display.viewOffset; - - for (var i = 0; i < cm.options.gutters.length; ++i) { - var g = display.gutters.childNodes[i]; - if (g && getRect(g).right >= mX) { - var line = lineAtHeight(cm.doc, mY); - var gutter = cm.options.gutters[i]; - signalLater(cm, "gutterClick", cm, line, gutter, e); - break; - } - } - return true; - } - function onDragStart(cm, e) { - if (eventInWidget(cm.display, e)) return; + if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; } + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return; var txt = cm.getSelection(); e.dataTransfer.setData("Text", txt); // Use dummy image instead of default browsers image. // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. - if (e.dataTransfer.setDragImage) { + if (e.dataTransfer.setDragImage && !safari) { var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); if (opera) { img.width = img.height = 1; @@ -1789,15 +1893,6 @@ window.CodeMirror = (function() { // Force a relayout, or Opera won't use our image for some obscure reason img._top = img.offsetTop; } - if (safari) { - if (cm.display.dragImg) { - img = cm.display.dragImg; - } else { - cm.display.dragImg = img; - img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; - cm.display.wrapper.appendChild(img); - } - } e.dataTransfer.setDragImage(img, 0, 0); if (opera) img.parentNode.removeChild(img); } @@ -1810,6 +1905,7 @@ window.CodeMirror = (function() { 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, []); + startWorker(cm, 100); } function setScrollLeft(cm, val, isScroller) { if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return; @@ -1847,6 +1943,11 @@ window.CodeMirror = (function() { if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail; else if (dy == null) dy = e.wheelDelta; + var display = cm.display, scroll = display.scroller; + // Quit if there's nothing to scroll here + if (!(dx && scroll.scrollWidth > scroll.clientWidth || + dy && scroll.scrollHeight > scroll.clientHeight)) 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 @@ -1860,7 +1961,6 @@ window.CodeMirror = (function() { } } - var display = cm.display, scroll = display.scroller; // 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 @@ -1938,8 +2038,10 @@ window.CodeMirror = (function() { var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto; clearTimeout(maybeTransition); if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() { - if (getKeyMap(cm.options.keyMap) == startMap) + if (getKeyMap(cm.options.keyMap) == startMap) { cm.options.keyMap = (next.call ? next.call(null, cm) : next); + keyMapChanged(cm); + } }, 50); var name = keyName(e, true), handled = false; @@ -1952,17 +2054,18 @@ window.CodeMirror = (function() { // 'go') bound to the keyname without 'Shift-'. handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);}) || lookupKey(name, keymaps, function(b) { - if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(cm, b); + if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) + return doHandleBinding(cm, b); }); } else { handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); }); } - if (handled == "stop") handled = false; if (handled) { e_preventDefault(e); restartBlink(cm); if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; } + signalLater(cm, "keyHandled", cm, name, e); } return handled; } @@ -1973,6 +2076,7 @@ window.CodeMirror = (function() { if (handled) { e_preventDefault(e); restartBlink(cm); + signalLater(cm, "keyHandled", cm, "'" + ch + "'", e); } return handled; } @@ -1981,8 +2085,8 @@ window.CodeMirror = (function() { function onKeyDown(e) { var cm = this; if (!cm.state.focused) onFocus(cm); - if (ie && e.keyCode == 27) { e.returnValue = false; } - if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; + if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; + if (ie && e.keyCode == 27) e.returnValue = false; var code = e.keyCode; // IE does strange things with escape. cm.doc.sel.shift = code == 16 || e.shiftKey; @@ -1998,7 +2102,7 @@ window.CodeMirror = (function() { function onKeyPress(e) { var cm = this; - if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; + if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; var keyCode = e.keyCode, charCode = e.charCode; if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return; @@ -2008,6 +2112,7 @@ window.CodeMirror = (function() { this.doc.mode.electricChars.indexOf(ch) > -1) setTimeout(operation(cm, function() {indentLine(cm, cm.doc.sel.to.line, "smart");}), 75); if (handleCharBinding(cm, e, ch)) return; + if (ie && !ie_lt9) cm.display.inputHasSelection = null; fastPoll(cm); } @@ -2018,7 +2123,10 @@ window.CodeMirror = (function() { cm.state.focused = true; if (cm.display.wrapper.className.search(/\bCodeMirror-focused\b/) == -1) cm.display.wrapper.className += " CodeMirror-focused"; - resetInput(cm, true); + if (!cm.curOp) { + resetInput(cm, true); + if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730 + } } slowPoll(cm); restartBlink(cm); @@ -2035,6 +2143,7 @@ window.CodeMirror = (function() { var detectingSelectAll; function onContextMenu(cm, e) { + if (signalDOMEvent(cm, e, "contextmenu")) return; var display = cm.display, sel = cm.doc.sel; if (eventInWidget(display, e)) return; @@ -2053,6 +2162,13 @@ window.CodeMirror = (function() { // Adds "Select all" to context menu in FF if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = " "; + function prepareSelectAllHack() { + if (display.input.selectionStart != null) { + var extval = display.input.value = " " + (posEq(sel.from, sel.to) ? "" : display.input.value); + display.prevInput = " "; + display.input.selectionStart = 1; display.input.selectionEnd = extval.length; + } + } function rehide() { display.inputDiv.style.position = "relative"; display.input.style.cssText = oldCSS; @@ -2060,12 +2176,10 @@ window.CodeMirror = (function() { slowPoll(cm); // Try to detect the user choosing select-all - if (display.input.selectionStart != null && (!ie || ie_lt9)) { + if (display.input.selectionStart != null) { + if (!ie || ie_lt9) prepareSelectAllHack(); clearTimeout(detectingSelectAll); - var extval = display.input.value = " " + (posEq(sel.from, sel.to) ? "" : display.input.value), i = 0; - display.prevInput = " "; - display.input.selectionStart = 1; display.input.selectionEnd = extval.length; - var poll = function(){ + var i = 0, poll = function(){ if (display.prevInput == " " && display.input.selectionStart == 0) operation(cm, commands.selectAll)(cm); else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500); @@ -2075,6 +2189,7 @@ window.CodeMirror = (function() { } } + if (ie && !ie_lt9) prepareSelectAllHack(); if (captureMiddleClick) { e_stop(e); var mouseup = function() { @@ -2089,10 +2204,11 @@ window.CodeMirror = (function() { // UPDATING - function changeEnd(change) { + var changeEnd = CodeMirror.changeEnd = function(change) { + if (!change.text) return change.to; return Pos(change.from.line + change.text.length - 1, lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)); - } + }; // Make sure a position will be valid after the given change. function clipPostChange(doc, change, pos) { @@ -2134,21 +2250,21 @@ window.CodeMirror = (function() { return {anchor: adjustPos(doc.sel.anchor), head: adjustPos(doc.sel.head)}; } - function filterChange(doc, change) { + function filterChange(doc, change, update) { var obj = { canceled: false, from: change.from, to: change.to, text: change.text, origin: change.origin, - update: function(from, to, text, origin) { - if (from) this.from = clipPos(doc, from); - if (to) this.to = clipPos(doc, to); - if (text) this.text = text; - if (origin !== undefined) this.origin = origin; - }, cancel: function() { this.canceled = true; } }; + if (update) obj.update = function(from, to, text, origin) { + if (from) this.from = clipPos(doc, from); + if (to) this.to = clipPos(doc, to); + if (text) this.text = text; + if (origin !== undefined) this.origin = origin; + }; signal(doc, "beforeChange", doc, obj); if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj); @@ -2165,7 +2281,7 @@ window.CodeMirror = (function() { } if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { - change = filterChange(doc, change); + change = filterChange(doc, change, true); if (!change) return; } @@ -2204,15 +2320,23 @@ window.CodeMirror = (function() { var hist = doc.history; var event = (type == "undo" ? hist.done : hist.undone).pop(); if (!event) return; - hist.dirtyCounter += type == "undo" ? -1 : 1; var anti = {changes: [], anchorBefore: event.anchorAfter, headBefore: event.headAfter, - anchorAfter: event.anchorBefore, headAfter: event.headBefore}; + anchorAfter: event.anchorBefore, headAfter: event.headBefore, + generation: hist.generation}; (type == "undo" ? hist.undone : hist.done).push(anti); + hist.generation = event.generation || ++hist.maxGeneration; + + var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange"); for (var i = event.changes.length - 1; i >= 0; --i) { var change = event.changes[i]; change.origin = type; + if (filter && !filterChange(doc, change, false)) { + (type == "undo" ? hist.done : hist.undone).length = 0; + return; + } + anti.changes.push(historyChangeFromChange(doc, change)); var after = i ? computeSelAfterChange(doc, change, null) @@ -2282,6 +2406,9 @@ window.CodeMirror = (function() { }); } + if (!posLess(doc.sel.head, change.from) && !posLess(change.to, doc.sel.head)) + cm.curOp.cursorActivity = true; + updateDoc(doc, change, spans, selAfter, estimateHeight(cm)); if (!cm.options.lineWrapping) { @@ -2408,7 +2535,8 @@ window.CodeMirror = (function() { sel.to = inv ? anchor : head; if (doc.cm) - doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true; + doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = + doc.cm.curOp.cursorActivity = true; signalLater(doc, "cursorActivity", doc); } @@ -2470,11 +2598,11 @@ window.CodeMirror = (function() { // SCROLLING function scrollCursorIntoView(cm) { - var coords = scrollPosIntoView(cm, cm.doc.sel.head); + var coords = scrollPosIntoView(cm, cm.doc.sel.head, cm.options.cursorScrollMargin); if (!cm.state.focused) return; - var display = cm.display, box = getRect(display.sizer), doScroll = null, pTop = paddingTop(cm.display); - if (coords.top + pTop + box.top < 0) doScroll = true; - else if (coords.bottom + pTop + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; + var display = cm.display, box = getRect(display.sizer), doScroll = null; + if (coords.top + box.top < 0) doScroll = true; + else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; if (doScroll != null && !phantom) { var hidden = display.cursor.style.display == "none"; if (hidden) { @@ -2512,13 +2640,17 @@ window.CodeMirror = (function() { } function calculateScrollPos(cm, x1, y1, x2, y2) { - var display = cm.display, pt = paddingTop(display); - y1 += pt; y2 += pt; + var display = cm.display, snapMargin = textHeight(cm.display); + if (y1 < 0) y1 = 0; var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {}; var docBottom = cm.doc.height + paddingVert(display); - var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10; - if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1); - else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen; + var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin; + if (y1 < screentop) { + result.scrollTop = atTop ? 0 : y1; + } else if (y2 > screentop + screen) { + var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen); + if (newTop != screentop) result.scrollTop = newTop; + } var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft; x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth; @@ -2534,7 +2666,8 @@ window.CodeMirror = (function() { } function updateScrollPos(cm, left, top) { - cm.curOp.updateScrollPos = {scrollLeft: left, scrollTop: top}; + cm.curOp.updateScrollPos = {scrollLeft: left == null ? cm.doc.scrollLeft : left, + scrollTop: top == null ? cm.doc.scrollTop : top}; } function addToScrollPos(cm, left, top) { @@ -2548,7 +2681,7 @@ window.CodeMirror = (function() { function indentLine(cm, n, how, aggressive) { var doc = cm.doc; - if (!how) how = "add"; + if (how == null) how = "add"; if (how == "smart") { if (!cm.doc.mode.indent) how = "prev"; else var state = getStateBefore(cm, n); @@ -2571,6 +2704,8 @@ window.CodeMirror = (function() { indentation = curSpace + cm.options.indentUnit; } else if (how == "subtract") { indentation = curSpace - cm.options.indentUnit; + } else if (typeof how == "number") { + indentation = curSpace + how; } indentation = Math.max(0, indentation); @@ -2595,7 +2730,7 @@ window.CodeMirror = (function() { } function findPosH(doc, pos, dir, unit, visually) { - var line = pos.line, ch = pos.ch; + var line = pos.line, ch = pos.ch, origDir = dir; var lineObj = getLine(doc, line); var possible = true; function findNextLine() { @@ -2634,7 +2769,7 @@ window.CodeMirror = (function() { if (dir > 0 && !moveOnce(!first)) break; } } - var result = skipAtomic(doc, Pos(line, ch), dir, true); + var result = skipAtomic(doc, Pos(line, ch), origDir, true); if (!possible) result.hitSide = true; return result; } @@ -2659,7 +2794,7 @@ window.CodeMirror = (function() { function findWordAt(line, pos) { var start = pos.ch, end = pos.ch; if (line) { - if (pos.after === false || end == line.length) --start; else ++end; + if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end; var startChar = line.charAt(start); var check = isWordChar(startChar) ? isWordChar : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} @@ -2680,6 +2815,7 @@ window.CodeMirror = (function() { // 'wrap f in an operation, performed on its `this` parameter' CodeMirror.prototype = { + constructor: CodeMirror, focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);}, setOption: function(option, value) { @@ -2699,7 +2835,7 @@ window.CodeMirror = (function() { removeKeyMap: function(map) { var maps = this.state.keyMaps; for (var i = 0; i < maps.length; ++i) - if ((typeof map == "string" ? maps[i].name : maps[i]) == map) { + if (maps[i] == map || (typeof maps[i] != "string" && maps[i].name == map)) { maps.splice(i, 1); return true; } @@ -2715,7 +2851,8 @@ window.CodeMirror = (function() { removeOverlay: operation(null, function(spec) { var overlays = this.state.overlays; for (var i = 0; i < overlays.length; ++i) { - if (overlays[i].modeSpec == spec) { + var cur = overlays[i].modeSpec; + if (cur == spec || typeof spec == "string" && cur.name == spec) { overlays.splice(i, 1); this.state.modeGen++; regChange(this); @@ -2725,7 +2862,7 @@ window.CodeMirror = (function() { }), indentLine: operation(null, function(n, dir, aggressive) { - if (typeof dir != "string") { + if (typeof dir != "string" && typeof dir != "number") { if (dir == null) dir = this.options.smartIndent ? "smart" : "prev"; else dir = dir ? "add" : "subtract"; } @@ -2740,10 +2877,10 @@ window.CodeMirror = (function() { // Fetch the parser token for a given character. Useful for hacks // that want to inspect the mode state (say, for completion). - getTokenAt: function(pos) { + getTokenAt: function(pos, precise) { var doc = this.doc; pos = clipPos(doc, pos); - var state = getStateBefore(this, pos.line), mode = this.doc.mode; + var state = getStateBefore(this, pos.line, precise), mode = this.doc.mode; var line = getLine(doc, pos.line); var stream = new StringStream(line.text, this.options.tabSize); while (stream.pos < pos.ch && !stream.eol()) { @@ -2758,10 +2895,37 @@ window.CodeMirror = (function() { state: state}; }, - getStateAfter: function(line) { + getTokenTypeAt: function(pos) { + pos = clipPos(this.doc, pos); + var styles = getLineStyles(this, getLine(this.doc, pos.line)); + var before = 0, after = (styles.length - 1) / 2, ch = pos.ch; + if (ch == 0) return styles[2]; + for (;;) { + var mid = (before + after) >> 1; + if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid; + else if (styles[mid * 2 + 1] < ch) before = mid + 1; + else return styles[mid * 2 + 2]; + } + }, + + getModeAt: function(pos) { + var mode = this.doc.mode; + if (!mode.innerMode) return mode; + return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode; + }, + + getHelper: function(pos, type) { + if (!helpers.hasOwnProperty(type)) return; + var help = helpers[type], mode = this.getModeAt(pos); + return mode[type] && help[mode[type]] || + mode.helperType && help[mode.helperType] || + help[mode.name]; + }, + + getStateAfter: function(line, precise) { var doc = this.doc; line = clipLine(doc, line == null ? doc.first + doc.size - 1: line); - return getStateBefore(this, line + 1); + return getStateBefore(this, line + 1, precise); }, cursorCoords: function(start, mode) { @@ -2781,6 +2945,19 @@ window.CodeMirror = (function() { return coordsChar(this, coords.left, coords.top); }, + lineAtHeight: function(height, mode) { + height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top; + return lineAtHeight(this.doc, height + this.display.viewOffset); + }, + heightAtLine: function(line, mode) { + var end = false, last = this.doc.first + this.doc.size - 1; + if (line < this.doc.first) line = this.doc.first; + else if (line > last) { line = last; end = true; } + var lineObj = getLine(this.doc, line); + return intoCoordSystem(this, getLine(this.doc, line), {top: 0, left: 0}, mode || "page").top + + (end ? lineObj.height : 0); + }, + defaultTextHeight: function() { return textHeight(this.display); }, defaultCharWidth: function() { return charWidth(this.display); }, @@ -2809,7 +2986,7 @@ window.CodeMirror = (function() { return changeLine(this, handle, function(line) { var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass"; if (!line[prop]) line[prop] = cls; - else if (new RegExp("\\b" + cls + "\\b").test(line[prop])) return false; + else if (new RegExp("(?:^|\\s)" + cls + "(?:$|\\s)").test(line[prop])) return false; else line[prop] += " " + cls; return true; }); @@ -2822,9 +2999,10 @@ window.CodeMirror = (function() { if (!cur) return false; else if (cls == null) line[prop] = null; else { - var upd = cur.replace(new RegExp("^" + cls + "\\b\\s*|\\s*\\b" + cls + "\\b"), ""); - if (upd == cur) return false; - line[prop] = upd || null; + var found = cur.match(new RegExp("(?:^|\\s+)" + cls + "(?:$|\\s+)")); + if (!found) return false; + var end = found.index + found[0].length; + line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null; } return true; }); @@ -2872,7 +3050,7 @@ window.CodeMirror = (function() { if (left + node.offsetWidth > hspace) left = hspace - node.offsetWidth; } - node.style.top = (top + paddingTop(display)) + "px"; + node.style.top = top + "px"; node.style.left = node.style.right = ""; if (horiz == "right") { left = display.sizer.clientWidth - node.offsetWidth; @@ -2940,7 +3118,8 @@ window.CodeMirror = (function() { sel.goalColumn = pos.left; }), - toggleOverwrite: function() { + toggleOverwrite: function(value) { + if (value != null && value == this.state.overwrite) return; if (this.state.overwrite = !this.state.overwrite) this.display.cursor.className += " CodeMirror-overwrite"; else @@ -2958,27 +3137,30 @@ window.CodeMirror = (function() { clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co}; }, - scrollIntoView: function(pos, margin) { + scrollIntoView: operation(null, function(pos, margin) { if (typeof pos == "number") pos = Pos(pos, 0); - if (!pos || pos.line != null) { - pos = pos ? clipPos(this.doc, pos) : this.doc.sel.head; - scrollPosIntoView(this, pos, margin); - } else { - scrollIntoView(this, pos.left, pos.top - margin, pos.right, pos.bottom + margin); - } - }, + if (!margin) margin = 0; + var coords = pos; - setSize: function(width, height) { + if (!pos || pos.line != null) { + this.curOp.scrollToPos = pos ? clipPos(this.doc, pos) : this.doc.sel.head; + this.curOp.scrollToPosMargin = margin; + coords = cursorCoords(this, this.curOp.scrollToPos); + } + var sPos = calculateScrollPos(this, coords.left, coords.top - margin, coords.right, coords.bottom + margin); + updateScrollPos(this, sPos.scrollLeft, sPos.scrollTop); + }), + + setSize: operation(null, function(width, height) { function interpret(val) { return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; } if (width != null) this.display.wrapper.style.width = interpret(width); if (height != null) this.display.wrapper.style.height = interpret(height); - this.refresh(); - }, - - on: function(type, f) {on(this, type, f);}, - off: function(type, f) {off(this, type, f);}, + if (this.options.lineWrapping) + this.display.measureLineCache.length = this.display.measureLineCachePos = 0; + this.curOp.forceUpdate = true; + }), operation: function(f){return runInOp(this, f);}, @@ -2993,6 +3175,7 @@ window.CodeMirror = (function() { old.cm = null; attachDoc(this, doc); clearCaches(this); + resetInput(this, true); updateScrollPos(this, doc.scrollLeft, doc.scrollTop); return old; }), @@ -3002,6 +3185,7 @@ window.CodeMirror = (function() { getScrollerElement: function(){return this.display.scroller;}, getGutterElement: function(){return this.display.gutters;} }; + eventMixin(CodeMirror); // OPTION DEFAULTS @@ -3058,6 +3242,7 @@ window.CodeMirror = (function() { cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; cm.refresh(); }, true); + option("coverGutterNextToScrollbar", false, updateScrollbars, true); option("lineNumbers", false, function(cm) { setGuttersForLineNumbers(cm.options); guttersChanged(cm); @@ -3073,13 +3258,19 @@ window.CodeMirror = (function() { option("dragDrop", true); option("cursorBlinkRate", 530); + option("cursorScrollMargin", 0); option("cursorHeight", 1); option("workTime", 100); option("workDelay", 100); option("flattenSpans", true); option("pollInterval", 100); option("undoDepth", 40, function(cm, val){cm.doc.history.undoDepth = val;}); + option("historyEventDelay", 500); option("viewportMargin", 10, function(cm){cm.refresh();}, true); + option("maxHighlightLength", 10000, function(cm){loadMode(cm); cm.refresh();}, true); + option("moveInputWithCursor", true, function(cm, val) { + if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0; + }); option("tabindex", null, function(cm, val) { cm.display.input.tabIndex = val || ""; @@ -3105,16 +3296,21 @@ window.CodeMirror = (function() { }; CodeMirror.resolveMode = function(spec) { - if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { spec = mimeModes[spec]; - else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + var found = mimeModes[spec.name]; + spec = createObj(found, spec); + spec.name = found.name; + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { return CodeMirror.resolveMode("application/xml"); + } if (typeof spec == "string") return {name: spec}; else return spec || {name: "null"}; }; CodeMirror.getMode = function(options, spec) { - spec = CodeMirror.resolveMode(spec); + var spec = CodeMirror.resolveMode(spec); var mfactory = modes[spec.name]; if (!mfactory) return CodeMirror.getMode(options, "text/plain"); var modeObj = mfactory(options, spec); @@ -3127,6 +3323,7 @@ window.CodeMirror = (function() { } } modeObj.name = spec.name; + return modeObj; }; @@ -3146,12 +3343,24 @@ window.CodeMirror = (function() { CodeMirror.defineExtension = function(name, func) { CodeMirror.prototype[name] = func; }; - + CodeMirror.defineDocExtension = function(name, func) { + Doc.prototype[name] = func; + }; CodeMirror.defineOption = option; var initHooks = []; CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; + var helpers = CodeMirror.helpers = {}; + CodeMirror.registerHelper = function(type, name, value) { + if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {}; + helpers[type][name] = value; + }; + + // UTILITIES + + CodeMirror.isWordChar = isWordChar; + // MODE STATE HANDLING // Utility functions for working with state. Exported because modes @@ -3177,6 +3386,7 @@ window.CodeMirror = (function() { CodeMirror.innerMode = function(mode, state) { while (mode.innerMode) { var info = mode.innerMode(state); + if (!info || info.mode == mode) break; state = info.state; mode = info.mode; } @@ -3197,6 +3407,10 @@ window.CodeMirror = (function() { var l = cm.getCursor().line; cm.replaceRange("", Pos(l, 0), Pos(l), "+delete"); }, + delLineLeft: function(cm) { + var cur = cm.getCursor(); + cm.replaceRange("", Pos(cur.line, 0), cur, "+delete"); + }, undo: function(cm) {cm.undo();}, redo: function(cm) {cm.redo();}, goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));}, @@ -3292,7 +3506,7 @@ window.CodeMirror = (function() { "Alt-Right": "goGroupRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "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-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delLineLeft", fallthrough: ["basic", "emacsy"] }; keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; @@ -3331,7 +3545,7 @@ window.CodeMirror = (function() { for (var i = 0; i < maps.length; ++i) { var done = lookup(maps[i]); - if (done) return done; + if (done) return done != "stop"; } } function isModifierKey(event) { @@ -3339,6 +3553,7 @@ window.CodeMirror = (function() { return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; } function keyName(event, noShift) { + if (opera && event.keyCode == 34 && event["char"]) return false; var name = keyNames[event.keyCode]; if (name == null || event.altGraphKey) return false; if (event.altKey) name = "Alt-" + name; @@ -3484,11 +3699,16 @@ window.CodeMirror = (function() { this.doc = doc; } CodeMirror.TextMarker = TextMarker; + eventMixin(TextMarker); TextMarker.prototype.clear = function() { if (this.explicitlyCleared) return; var cm = this.doc.cm, withOp = cm && !cm.curOp; if (withOp) startOperation(cm); + if (hasHandler(this, "clear")) { + var found = this.find(); + if (found) signalLater(this, "clear", found.from, found.to); + } var min = null, max = null; for (var i = 0; i < this.lines.length; ++i) { var line = this.lines[i]; @@ -3512,12 +3732,11 @@ window.CodeMirror = (function() { if (min != null && cm) regChange(cm, min, max + 1); this.lines.length = 0; this.explicitlyCleared = true; - if (this.collapsed && this.doc.cantEdit) { + if (this.atomic && this.doc.cantEdit) { this.doc.cantEdit = false; if (cm) reCheckSelection(cm); } if (withOp) endOperation(cm); - signalLater(this, "clear"); }; TextMarker.prototype.find = function() { @@ -3535,15 +3754,21 @@ window.CodeMirror = (function() { return from && {from: from, to: to}; }; - TextMarker.prototype.getOptions = function(copyWidget) { - var repl = this.replacedWith; - return {className: this.className, - inclusiveLeft: this.inclusiveLeft, inclusiveRight: this.inclusiveRight, - atomic: this.atomic, - collapsed: this.collapsed, - replacedWith: copyWidget ? repl && repl.cloneNode(true) : repl, - readOnly: this.readOnly, - startStyle: this.startStyle, endStyle: this.endStyle}; + TextMarker.prototype.changed = function() { + var pos = this.find(), cm = this.doc.cm; + if (!pos || !cm) return; + if (this.type != "bookmark") pos = pos.from; + var line = getLine(this.doc, pos.line); + clearCachedMeasurement(cm, line); + if (pos.line >= cm.display.showingFrom && pos.line < cm.display.showingTo) { + for (var node = cm.display.lineDiv.firstChild; node; node = node.nextSibling) if (node.lineObj == line) { + if (node.offsetHeight != line.height) updateLineHeight(line, node.offsetHeight); + break; + } + runInOp(cm, function() { + cm.curOp.selectionChanged = cm.curOp.forceUpdate = cm.curOp.updateMaxLine = true; + }); + } }; TextMarker.prototype.attachLine = function(line) { @@ -3572,9 +3797,14 @@ window.CodeMirror = (function() { if (marker.replacedWith) { marker.collapsed = true; marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget"); + if (!options.handleMouseEvents) marker.replacedWith.ignoreEvents = true; } if (marker.collapsed) sawCollapsedSpans = true; + if (marker.addToHistory) + addToHistory(doc, {from: from, to: to, origin: "markText"}, + {head: doc.sel.head, anchor: doc.sel.anchor}, NaN); + var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd, cm = doc.cm, updateMaxLine; doc.iter(curLine, to.line + 1, function(line) { if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.display.maxLine) @@ -3610,7 +3840,7 @@ window.CodeMirror = (function() { } if (cm) { if (updateMaxLine) cm.curOp.updateMaxLine = true; - if (marker.className || marker.startStyle || marker.endStyle || marker.collapsed) + if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.collapsed) regChange(cm, from.line, to.line + 1); if (marker.atomic) reCheckSelection(cm); } @@ -3628,6 +3858,7 @@ window.CodeMirror = (function() { } } CodeMirror.SharedTextMarker = SharedTextMarker; + eventMixin(SharedTextMarker); SharedTextMarker.prototype.clear = function() { if (this.explicitlyCleared) return; @@ -3639,11 +3870,6 @@ window.CodeMirror = (function() { SharedTextMarker.prototype.find = function() { return this.primary.find(); }; - SharedTextMarker.prototype.getOptions = function(copyWidget) { - var inner = this.primary.getOptions(copyWidget); - inner.shared = true; - return inner; - }; function markTextShared(doc, from, to, options, type) { options = copyObj(options); @@ -3746,6 +3972,13 @@ window.CodeMirror = (function() { } } } + if (sameLine && first) { + // Make sure we didn't create any zero-length spans + for (var i = 0; i < first.length; ++i) + if (first[i].from != null && first[i].from == first[i].to && first[i].marker.type != "bookmark") + first.splice(i--, 1); + if (!first.length) first = null; + } var newMarkers = [first]; if (!sameLine) { @@ -3840,6 +4073,7 @@ window.CodeMirror = (function() { sp = sps[i]; if (!sp.marker.collapsed) continue; if (sp.from == null) return true; + if (sp.marker.replacedWith) continue; if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) return true; } @@ -3853,7 +4087,7 @@ window.CodeMirror = (function() { return true; for (var sp, i = 0; i < line.markedSpans.length; ++i) { sp = line.markedSpans[i]; - if (sp.marker.collapsed && sp.from == span.to && + if (sp.marker.collapsed && !sp.marker.replacedWith && sp.from == span.to && (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && lineIsHiddenInner(doc, line, sp)) return true; } @@ -3877,11 +4111,12 @@ window.CodeMirror = (function() { // LINE WIDGETS var LineWidget = CodeMirror.LineWidget = function(cm, node, options) { - for (var opt in options) if (options.hasOwnProperty(opt)) + if (options) for (var opt in options) if (options.hasOwnProperty(opt)) this[opt] = options[opt]; this.cm = cm; this.node = node; }; + eventMixin(LineWidget); function widgetOperation(f) { return function() { var withOp = !this.cm.curOp; @@ -3896,7 +4131,9 @@ window.CodeMirror = (function() { if (no == null || !ws) return; for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1); if (!ws.length) this.line.widgets = null; + var aboveVisible = heightAtLine(this.cm, this.line) < this.cm.doc.scrollTop; updateLineHeight(this.line, Math.max(0, this.line.height - widgetHeight(this))); + if (aboveVisible) addToScrollPos(this.cm, 0, -this.height); regChange(this.cm, no, no + 1); }); LineWidget.prototype.changed = widgetOperation(function() { @@ -3920,10 +4157,12 @@ window.CodeMirror = (function() { var widget = new LineWidget(cm, node, options); if (widget.noHScroll) cm.display.alignWidgets = true; changeLine(cm, handle, function(line) { - (line.widgets || (line.widgets = [])).push(widget); + var widgets = line.widgets || (line.widgets = []); + if (widget.insertAt == null) widgets.push(widget); + else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); widget.line = line; if (!lineIsHidden(cm.doc, line) || widget.showIfHidden) { - var aboveVisible = heightAtLine(cm, line) < cm.display.scroller.scrollTop; + var aboveVisible = heightAtLine(cm, line) < cm.doc.scrollTop; updateLineHeight(line, line.height + widgetHeight(widget)); if (aboveVisible) addToScrollPos(cm, 0, widget.height); } @@ -3936,12 +4175,12 @@ window.CodeMirror = (function() { // Line objects. These hold state related to a line, including // highlighting info (the styles array). - function makeLine(text, markedSpans, estimateHeight) { - var line = {text: text}; - attachMarkedSpans(line, markedSpans); - line.height = estimateHeight ? estimateHeight(line) : 1; - return line; - } + var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) { + this.text = text; + attachMarkedSpans(this, markedSpans); + this.height = estimateHeight ? estimateHeight(this) : 1; + }; + eventMixin(Line); function updateLine(line, text, markedSpans, estimateHeight) { line.text = text; @@ -3965,25 +4204,25 @@ window.CodeMirror = (function() { function runMode(cm, text, mode, state, f) { var flattenSpans = mode.flattenSpans; if (flattenSpans == null) flattenSpans = cm.options.flattenSpans; - var curText = "", curStyle = null; - var stream = new StringStream(text, cm.options.tabSize); + var curStart = 0, curStyle = null; + var stream = new StringStream(text, cm.options.tabSize), style; if (text == "" && mode.blankLine) mode.blankLine(state); while (!stream.eol()) { - var style = mode.token(stream, state); - if (stream.pos > 5000) { + if (stream.pos > cm.options.maxHighlightLength) { flattenSpans = false; // Webkit seems to refuse to render text nodes longer than 57444 characters stream.pos = Math.min(text.length, stream.start + 50000); style = null; + } else { + style = mode.token(stream, state); } - var substr = stream.current(); - stream.start = stream.pos; if (!flattenSpans || curStyle != style) { - if (curText) f(curText, curStyle); - curText = substr; curStyle = style; - } else curText = curText + substr; + if (curStart < stream.start) f(stream.start, curStyle); + curStart = stream.start; curStyle = style; + } + stream.start = stream.pos; } - if (curText) f(curText, curStyle); + if (curStart < stream.pos) f(stream.pos, curStyle); } function highlightLine(cm, line, state) { @@ -3991,27 +4230,24 @@ window.CodeMirror = (function() { // mode/overlays that it is based on (for easy invalidation). var st = [cm.state.modeGen]; // Compute the base array of styles - runMode(cm, line.text, cm.doc.mode, state, function(txt, style) {st.push(txt, style);}); + runMode(cm, line.text, cm.doc.mode, state, function(end, style) {st.push(end, style);}); // Run overlays, adjust style array. for (var o = 0; o < cm.state.overlays.length; ++o) { - var overlay = cm.state.overlays[o], i = 1; - runMode(cm, line.text, overlay.mode, true, function(txt, style) { - var start = i, len = txt.length; + var overlay = cm.state.overlays[o], i = 1, at = 0; + runMode(cm, line.text, overlay.mode, true, function(end, style) { + var start = i; // Ensure there's a token end at the current position, and that i points at it - while (len) { - var cur = st[i], len_ = cur.length; - if (len_ <= len) { - len -= len_; - } else { - st.splice(i, 1, cur.slice(0, len), st[i+1], cur.slice(len)); - len = 0; - } + while (at < end) { + var i_end = st[i]; + if (i_end > end) + st.splice(i, 1, end, st[i+1], i_end); i += 2; + at = Math.min(end, i_end); } if (!style) return; if (overlay.opaque) { - st.splice(start, i - start, txt, style); + st.splice(start, i - start, end, style); i = start + 2; } else { for (; start < i; start += 2) { @@ -4037,7 +4273,7 @@ window.CodeMirror = (function() { var mode = cm.doc.mode; var stream = new StringStream(line.text, cm.options.tabSize); if (line.text == "" && mode.blankLine) mode.blankLine(state); - while (!stream.eol() && stream.pos <= 5000) { + while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) { mode.token(stream, state); stream.start = stream.pos; } @@ -4050,38 +4286,33 @@ window.CodeMirror = (function() { (styleToClassCache[style] = "cm-" + style.replace(/ +/g, " cm-")); } - function lineContent(cm, realLine, measure) { - var merged, line = realLine, lineBefore, sawBefore, simple = true; - while (merged = collapsedSpanAtStart(line)) { - simple = false; + function lineContent(cm, realLine, measure, copyWidgets) { + var merged, line = realLine, empty = true; + while (merged = collapsedSpanAtStart(line)) line = getLine(cm.doc, merged.find().from.line); - if (!lineBefore) lineBefore = line; - } - var builder = {pre: elt("pre"), col: 0, pos: 0, display: !measure, - measure: null, addedOne: false, cm: cm}; + var builder = {pre: elt("pre"), col: 0, pos: 0, + measure: null, measuredSomething: false, cm: cm, + copyWidgets: copyWidgets}; if (line.textClass) builder.pre.className = line.textClass; do { + if (line.text) empty = false; builder.measure = line == realLine && measure; builder.pos = 0; builder.addToken = builder.measure ? buildTokenMeasure : buildToken; if ((ie || webkit) && cm.getOption("lineWrapping")) builder.addToken = buildTokenSplitSpaces(builder.addToken); - if (measure && sawBefore && line != realLine && !builder.addedOne) { - measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure)); - builder.addedOne = true; - } var next = insertLineContent(line, builder, getLineStyles(cm, line)); - sawBefore = line == lineBefore; - if (next) { - line = getLine(cm.doc, next.to.line); - simple = false; + if (measure && line == realLine && !builder.measuredSomething) { + measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure)); + builder.measuredSomething = true; } + if (next) line = getLine(cm.doc, next.to.line); } while (next); - if (measure && !builder.addedOne) - measure[0] = builder.pre.appendChild(simple ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure)); + if (measure && !builder.measuredSomething && !measure[0]) + measure[0] = builder.pre.appendChild(empty ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure)); if (!builder.pre.firstChild && !lineIsHidden(cm.doc, realLine)) builder.pre.appendChild(document.createTextNode("\u00a0")); @@ -4105,7 +4336,7 @@ window.CodeMirror = (function() { } var tokenSpecialChars = /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\uFEFF]/g; - function buildToken(builder, text, style, startStyle, endStyle) { + function buildToken(builder, text, style, startStyle, endStyle, title) { if (!text) return; if (!tokenSpecialChars.test(text)) { builder.col += text.length; @@ -4138,7 +4369,9 @@ window.CodeMirror = (function() { var fullStyle = style || ""; if (startStyle) fullStyle += startStyle; if (endStyle) fullStyle += endStyle; - return builder.pre.appendChild(elt("span", [content], fullStyle)); + var token = elt("span", [content], fullStyle); + if (title) token.title = title; + return builder.pre.appendChild(token); } builder.pre.appendChild(content); } @@ -4150,13 +4383,14 @@ window.CodeMirror = (function() { if (ch >= "\ud800" && ch < "\udbff" && i < text.length - 1) { ch = text.slice(i, i + 2); ++i; - } else if (i && wrapping && - spanAffectsWrapping.test(text.slice(i - 1, i + 1))) { + } else if (i && wrapping && spanAffectsWrapping(text, i)) { builder.pre.appendChild(elt("wbr")); } + var old = builder.measure[builder.pos]; var span = builder.measure[builder.pos] = buildToken(builder, ch, style, start && startStyle, i == text.length - 1 && endStyle); + if (old) span.leftSide = old.leftSide || old; // In IE single-space nodes wrap differently than spaces // embedded in larger text nodes, except when set to // white-space: normal (issue #1268). @@ -4165,7 +4399,7 @@ window.CodeMirror = (function() { span.style.whiteSpace = "normal"; builder.pos += ch.length; } - if (text.length) builder.addedOne = true; + if (text.length) builder.measuredSomething = true; } function buildTokenSplitSpaces(inner) { @@ -4175,18 +4409,29 @@ window.CodeMirror = (function() { out += " "; return out; } - return function(builder, text, style, startStyle, endStyle) { - return inner(builder, text.replace(/ {3,}/, split), style, startStyle, endStyle); + return function(builder, text, style, startStyle, endStyle, title) { + return inner(builder, text.replace(/ {3,}/, split), style, startStyle, endStyle, title); }; } - function buildCollapsedSpan(builder, size, widget) { + function buildCollapsedSpan(builder, size, marker, ignoreWidget) { + var widget = !ignoreWidget && marker.replacedWith; if (widget) { - if (!builder.display) widget = widget.cloneNode(true); + if (builder.copyWidgets) widget = widget.cloneNode(true); builder.pre.appendChild(widget); - if (builder.measure && size) { - builder.measure[builder.pos] = widget; - builder.addedOne = true; + if (builder.measure) { + if (size) { + builder.measure[builder.pos] = widget; + } else { + var elt = zeroWidthElement(builder.cm.display.measure); + if (marker.type == "bookmark" && !marker.insertLeft) + builder.measure[builder.pos] = builder.pre.appendChild(elt); + else if (builder.measure[builder.pos]) + return; + else + builder.measure[builder.pos] = builder.pre.insertBefore(elt, widget); + } + builder.measuredSomething = true; } } builder.pos += size; @@ -4195,21 +4440,20 @@ window.CodeMirror = (function() { // Outputs a number of spans to make up a line, taking highlighting // and marked text into account. function insertLineContent(line, builder, styles) { - var spans = line.markedSpans; + var spans = line.markedSpans, allText = line.text, at = 0; if (!spans) { for (var i = 1; i < styles.length; i+=2) - builder.addToken(builder, styles[i], styleToClass(styles[i+1])); + builder.addToken(builder, allText.slice(at, at = styles[i]), styleToClass(styles[i+1])); return; } - var allText = line.text, len = allText.length; - var pos = 0, i = 1, text = "", style; - var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed; + var len = allText.length, pos = 0, i = 1, text = "", style; + var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed; for (;;) { if (nextChange == pos) { // Update current marker set - spanStyle = spanEndStyle = spanStartStyle = ""; + spanStyle = spanEndStyle = spanStartStyle = title = ""; collapsed = null; nextChange = Infinity; - var foundBookmark = null; + var foundBookmarks = []; for (var j = 0; j < spans.length; ++j) { var sp = spans[j], m = sp.marker; if (sp.from <= pos && (sp.to == null || sp.to > pos)) { @@ -4217,20 +4461,21 @@ window.CodeMirror = (function() { if (m.className) spanStyle += " " + m.className; if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle; - if (m.collapsed && (!collapsed || collapsed.marker.width < m.width)) + if (m.title && !title) title = m.title; + if (m.collapsed && (!collapsed || collapsed.marker.size < m.size)) collapsed = sp; } else if (sp.from > pos && nextChange > sp.from) { nextChange = sp.from; } - if (m.type == "bookmark" && sp.from == pos && m.replacedWith) - foundBookmark = m.replacedWith; + if (m.type == "bookmark" && sp.from == pos && m.replacedWith) foundBookmarks.push(m); } if (collapsed && (collapsed.from || 0) == pos) { buildCollapsedSpan(builder, (collapsed.to == null ? len : collapsed.to) - pos, - collapsed.from != null && collapsed.marker.replacedWith); + collapsed.marker, collapsed.from == null); if (collapsed.to == null) return collapsed.marker.find(); } - if (foundBookmark && !collapsed) buildCollapsedSpan(builder, 0, foundBookmark); + if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j) + buildCollapsedSpan(builder, 0, foundBookmarks[j]); } if (pos >= len) break; @@ -4241,13 +4486,14 @@ window.CodeMirror = (function() { if (!collapsed) { var tokenText = end > upto ? text.slice(0, upto - pos) : text; builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, - spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : ""); + spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title); } if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} pos = end; spanStartStyle = ""; } - text = styles[i++]; style = styleToClass(styles[i++]); + text = allText.slice(at, at = styles[i++]); + style = styleToClass(styles[i++]); } } } @@ -4270,7 +4516,7 @@ window.CodeMirror = (function() { // This is a whole-line replace. Treated specially to make // sure line objects move the way they are supposed to. for (var i = 0, e = text.length - 1, added = []; i < e; ++i) - added.push(makeLine(text[i], spansFor(i), estimateHeight)); + added.push(new Line(text[i], spansFor(i), estimateHeight)); update(lastLine, lastLine.text, lastSpans); if (nlines) doc.remove(from.line, nlines); if (added.length) doc.insert(from.line, added); @@ -4279,8 +4525,8 @@ window.CodeMirror = (function() { update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); } else { for (var added = [], i = 1, e = text.length - 1; i < e; ++i) - added.push(makeLine(text[i], spansFor(i), estimateHeight)); - added.push(makeLine(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); + added.push(new Line(text[i], spansFor(i), estimateHeight)); + added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); doc.insert(from.line + 1, added); } @@ -4291,7 +4537,7 @@ window.CodeMirror = (function() { update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); for (var i = 1, e = text.length - 1, added = []; i < e; ++i) - added.push(makeLine(text[i], spansFor(i), estimateHeight)); + added.push(new Line(text[i], spansFor(i), estimateHeight)); if (nlines > 1) doc.remove(from.line + 1, nlines - 1); doc.insert(from.line + 1, added); } @@ -4434,11 +4680,12 @@ window.CodeMirror = (function() { if (!(this instanceof Doc)) return new Doc(text, mode, firstLine); if (firstLine == null) firstLine = 0; - BranchChunk.call(this, [new LeafChunk([makeLine("", null)])]); + BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); this.first = firstLine; this.scrollTop = this.scrollLeft = 0; this.cantEdit = false; this.history = makeHistory(); + this.cleanGeneration = 1; this.frontier = firstLine; var start = Pos(firstLine, 0); this.sel = {from: start, to: start, head: start, anchor: start, shift: false, extend: false, goalColumn: null}; @@ -4450,6 +4697,7 @@ window.CodeMirror = (function() { }; Doc.prototype = createObj(BranchChunk.prototype, { + constructor: Doc, iter: function(from, to, op) { if (op) this.iterN(from - this.first, to - from, op); else this.iterN(this.first, this.first + this.size, from); @@ -4490,13 +4738,18 @@ window.CodeMirror = (function() { replaceRange(this, text, Pos(line, 0), clipPos(this, Pos(line))); }, removeLine: function(line) { - if (isLine(this, line)) - replaceRange(this, "", Pos(line, 0), clipPos(this, Pos(line + 1, 0))); + if (line) replaceRange(this, "", clipPos(this, Pos(line - 1)), clipPos(this, Pos(line))); + else replaceRange(this, "", Pos(0, 0), clipPos(this, Pos(1, 0))); }, getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);}, getLineNumber: function(line) {return lineNo(line);}, + getLineHandleVisualStart: function(line) { + if (typeof line == "number") line = getLine(this, line); + return visualLine(this, line); + }, + lineCount: function() {return this.size;}, firstLine: function() {return this.first;}, lastLine: function() {return this.first + this.size - 1;}, @@ -4518,11 +4771,11 @@ window.CodeMirror = (function() { if (extend) extendSelection(this, pos); else setSelection(this, pos, pos); }), - setSelection: docOperation(function(anchor, head) { - setSelection(this, clipPos(this, anchor), clipPos(this, head || anchor)); + setSelection: docOperation(function(anchor, head, bias) { + setSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), bias); }), - extendSelection: docOperation(function(from, to) { - extendSelection(this, clipPos(this, from), to && clipPos(this, to)); + extendSelection: docOperation(function(from, to, bias) { + extendSelection(this, clipPos(this, from), to && clipPos(this, to), bias); }), getSelection: function(lineSep) {return this.getRange(this.sel.from, this.sel.to, lineSep);}, @@ -4538,20 +4791,25 @@ window.CodeMirror = (function() { var hist = this.history; return {undo: hist.done.length, redo: hist.undone.length}; }, - clearHistory: function() {this.history = makeHistory();}, + clearHistory: function() {this.history = makeHistory(this.history.maxGeneration);}, markClean: function() { - this.history.dirtyCounter = 0; - this.history.lastOp = this.history.lastOrigin = null; + this.cleanGeneration = this.changeGeneration(); + }, + changeGeneration: function() { + this.history.lastOp = this.history.lastOrigin = null; + return this.history.generation; + }, + isClean: function (gen) { + return this.history.generation == (gen || this.cleanGeneration); }, - isClean: function () {return this.history.dirtyCounter == 0;}, getHistory: function() { return {done: copyHistoryArray(this.history.done), undone: copyHistoryArray(this.history.undone)}; }, setHistory: function(histData) { - var hist = this.history = makeHistory(); + var hist = this.history = makeHistory(this.history.maxGeneration); hist.done = histData.done.slice(0); hist.undone = histData.undone.slice(0); }, @@ -4662,6 +4920,8 @@ window.CodeMirror = (function() { return function() {return method.apply(this.doc, arguments);}; })(Doc.prototype[prop]); + eventMixin(Doc); + function linkedDocs(doc, f, sharedHistOnly) { function propagate(doc, skip, sharedHist) { if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) { @@ -4781,7 +5041,7 @@ window.CodeMirror = (function() { // HISTORY - function makeHistory() { + function makeHistory(startGen) { return { // Arrays of history events. Doing something adds an event to // done and clears undo. Undoing moves events from done to @@ -4791,7 +5051,7 @@ window.CodeMirror = (function() { // event lastTime: 0, lastOp: null, lastOrigin: null, // Used by the isClean() method - dirtyCounter: 0 + generation: startGen || 1, maxGeneration: startGen || 1 }; } @@ -4805,7 +5065,8 @@ window.CodeMirror = (function() { } function historyChangeFromChange(doc, change) { - var histChange = {from: change.from, to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; + var from = { line: change.from.line, ch: change.from.ch }; + var histChange = {from: from, to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true); return histChange; @@ -4819,7 +5080,8 @@ window.CodeMirror = (function() { if (cur && (hist.lastOp == opId || hist.lastOrigin == change.origin && change.origin && - ((change.origin.charAt(0) == "+" && hist.lastTime > time - 600) || change.origin.charAt(0) == "*"))) { + ((change.origin.charAt(0) == "+" && doc.cm && hist.lastTime > time - doc.cm.options.historyEventDelay) || + change.origin.charAt(0) == "*"))) { // Merge this change into the last event var last = lst(cur.changes); if (posEq(change.from, change.to) && posEq(change.from, last.to)) { @@ -4834,17 +5096,13 @@ window.CodeMirror = (function() { } else { // Can not be merged, start a new event. cur = {changes: [historyChangeFromChange(doc, change)], + generation: hist.generation, anchorBefore: doc.sel.anchor, headBefore: doc.sel.head, anchorAfter: selAfter.anchor, headAfter: selAfter.head}; hist.done.push(cur); + hist.generation = ++hist.maxGeneration; while (hist.done.length > hist.undoDepth) hist.done.shift(); - if (hist.dirtyCounter < 0) - // The user has made a change after undoing past the last clean state. - // We can never get back to a clean state now until markClean() is called. - hist.dirtyCounter = NaN; - else - hist.dirtyCounter++; } hist.lastTime = time; hist.lastOp = opId; @@ -4959,6 +5217,9 @@ window.CodeMirror = (function() { if (e.stopPropagation) e.stopPropagation(); else e.cancelBubble = true; } + function e_defaultPrevented(e) { + return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false; + } function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);} CodeMirror.e_stop = e_stop; CodeMirror.e_preventDefault = e_preventDefault; @@ -5025,6 +5286,11 @@ window.CodeMirror = (function() { delayedCallbacks.push(bnd(arr[i])); } + function signalDOMEvent(cm, e, override) { + signal(cm, override || e.type, cm, e); + return e_defaultPrevented(e) || e.codemirrorIgnore; + } + function fireDelayed() { --delayedCallbackDepth; var delayed = delayedCallbacks; @@ -5039,6 +5305,11 @@ window.CodeMirror = (function() { CodeMirror.on = on; CodeMirror.off = off; CodeMirror.signal = signal; + function eventMixin(ctor) { + ctor.prototype.on = function(type, f) {on(this, type, f);}; + ctor.prototype.off = function(type, f) {off(this, type, f);}; + } + // MISC UTILITIES // Number of pixels added to scroller and sizer to hide scrollbar @@ -5079,7 +5350,11 @@ window.CodeMirror = (function() { if (ios) { // Mobile Safari apparently has a bug where select() is broken. node.selectionStart = 0; node.selectionEnd = node.value.length; - } else node.select(); + } else { + // Suppress mysterious IE10 errors + try { node.select(); } + catch(_e) {} + } } function indexOf(collection, elt) { @@ -5113,7 +5388,7 @@ window.CodeMirror = (function() { return function(){return f.apply(null, args);}; } - var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc]/; + var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; function isWordChar(ch) { return /\w/.test(ch) || ch > "\x80" && (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); @@ -5174,13 +5449,26 @@ window.CodeMirror = (function() { // word wrapping between certain characters *only* if a new inline // element is started between them. This makes it hard to reliably // measure the position of things, since that requires inserting an - // extra span. This terribly fragile set of regexps matches the + // extra span. This terribly fragile set of tests matches the // character combinations that suffer from this phenomenon on the // various browsers. - var spanAffectsWrapping = /^$/; // Won't match any two-character string - if (gecko) spanAffectsWrapping = /$'/; - else if (safari && !/Version\/([6-9]|\d\d)\b/.test(navigator.userAgent)) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/; - else if (webkit) spanAffectsWrapping = /[~!#%&*)=+}\]|\"\.>,:;][({[<]|-[^\-?\.]|\?[\w~`@#$%\^&*(_=+{[|><]/; + function spanAffectsWrapping() { return false; } + if (gecko) // Only for "$'" + spanAffectsWrapping = function(str, i) { + return str.charCodeAt(i - 1) == 36 && str.charCodeAt(i) == 39; + }; + else if (safari && !/Version\/([6-9]|\d\d)\b/.test(navigator.userAgent)) + spanAffectsWrapping = function(str, i) { + return /\-[^ \-?]|\?[^ !\'\"\),.\-\/:;\?\]\}]/.test(str.slice(i - 1, i + 1)); + }; + else if (webkit && !/Chrome\/(?:29|[3-9]\d|\d\d\d)\./.test(navigator.userAgent)) + spanAffectsWrapping = function(str, i) { + if (i > 1 && str.charCodeAt(i - 1) == 45) { + if (/\w/.test(str.charAt(i - 2)) && /[^\-?\.]/.test(str.charAt(i))) return true; + if (i > 2 && /[\d\.,]/.test(str.charAt(i - 2)) && /[\d\.,]/.test(str.charAt(i))) return false; + } + return /[~!#%&*)=+}\]|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|…[\w~`@#$%\^&*(_=+{[><]/.test(str.slice(i - 1, i + 1)); + }; var knownScrollbarWidth; function scrollbarWidth(measure) { @@ -5265,11 +5553,15 @@ window.CodeMirror = (function() { function iterateBidiSections(order, from, to, f) { if (!order) return f(from, to, "ltr"); + var found = false; for (var i = 0; i < order.length; ++i) { var part = order[i]; - if (part.from < to && part.to > from || from == to && part.to == from) + if (part.from < to && part.to > from || from == to && part.to == from) { f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); + found = true; + } } + if (!found) f(from, to, "ltr"); } function bidiLeft(part) { return part.level % 2 ? part.to : part.from; } @@ -5299,6 +5591,40 @@ window.CodeMirror = (function() { return Pos(lineN, ch); } + function compareBidiLevel(order, a, b) { + var linedir = order[0].level; + if (a == linedir) return true; + if (b == linedir) return false; + return a < b; + } + var bidiOther; + function getBidiPartAt(order, pos) { + for (var i = 0, found; i < order.length; ++i) { + var cur = order[i]; + if (cur.from < pos && cur.to > pos) { bidiOther = null; return i; } + if (cur.from == pos || cur.to == pos) { + if (found == null) { + found = i; + } else if (compareBidiLevel(order, cur.level, order[found].level)) { + bidiOther = found; + return i; + } else { + bidiOther = i; + return found; + } + } + } + bidiOther = null; + return found; + } + + function moveInLine(line, pos, dir, byUnit) { + if (!byUnit) return pos + dir; + do pos += dir; + while (pos > 0 && isExtendingChar.test(line.text.charAt(pos))); + return pos; + } + // This is somewhat involved. It is needed in order to move // 'visually' through bi-directional text -- i.e., pressing left // should make the cursor go left, even when in RTL text. The @@ -5308,37 +5634,24 @@ window.CodeMirror = (function() { function moveVisually(line, start, dir, byUnit) { var bidi = getOrder(line); if (!bidi) return moveLogically(line, start, dir, byUnit); - var moveOneUnit = byUnit ? function(pos, dir) { - do pos += dir; - while (pos > 0 && isExtendingChar.test(line.text.charAt(pos))); - return pos; - } : function(pos, dir) { return pos + dir; }; - var linedir = bidi[0].level; - for (var i = 0; i < bidi.length; ++i) { - var part = bidi[i], sticky = part.level % 2 == linedir; - if ((part.from < start && part.to > start) || - (sticky && (part.from == start || part.to == start))) break; - } - var target = moveOneUnit(start, part.level % 2 ? -dir : dir); + var pos = getBidiPartAt(bidi, start), part = bidi[pos]; + var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit); - while (target != null) { - if (part.level % 2 == linedir) { - if (target < part.from || target > part.to) { - part = bidi[i += dir]; - target = part && (dir > 0 == part.level % 2 ? moveOneUnit(part.to, -1) : moveOneUnit(part.from, 1)); - } else break; + for (;;) { + if (target > part.from && target < part.to) return target; + if (target == part.from || target == part.to) { + if (getBidiPartAt(bidi, target) == pos) return target; + part = bidi[pos += dir]; + return (dir > 0) == part.level % 2 ? part.to : part.from; } else { - if (target == bidiLeft(part)) { - part = bidi[--i]; - target = part && bidiRight(part); - } else if (target == bidiRight(part)) { - part = bidi[++i]; - target = part && bidiLeft(part); - } else break; + part = bidi[pos += dir]; + if (!part) return null; + if ((dir > 0) == part.level % 2) + target = moveInLine(line, part.to, -1, byUnit); + else + target = moveInLine(line, part.from, 1, byUnit); } } - - return target < 0 || target > line.text.length ? null : target; } function moveLogically(line, start, dir, byUnit) { @@ -5510,7 +5823,7 @@ window.CodeMirror = (function() { // THE END - CodeMirror.version = "3.11"; + CodeMirror.version = "3.16.0"; return CodeMirror; })();