UNPKG

jsonminify

Version:

JSON.minify() minifies blocks of JSON-like content into valid JSON by removing all whitespace *and* comments.

1,204 lines (1,086 loc) 178 kB
// CodeMirror version 3.0 // // CodeMirror is the only global var we claim window.CodeMirror = (function() { "use strict"; // BROWSER SNIFFING // Crude, but necessary to handle a number of hard-to-feature-detect // bugs and behavior differences. var gecko = /gecko\/\d/i.test(navigator.userAgent); var ie = /MSIE \d/.test(navigator.userAgent); var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent); var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent); var webkit = /WebKit\//.test(navigator.userAgent); var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent); var chrome = /Chrome\//.test(navigator.userAgent); var opera = /Opera\//.test(navigator.userAgent); var safari = /Apple Computer/.test(navigator.vendor); var khtml = /KHTML\//.test(navigator.userAgent); var mac_geLion = /Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent); var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent); var phantom = /PhantomJS/.test(navigator.userAgent); var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); // This is woefully incomplete. Suggestions for alternative methods welcome. var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|IEMobile/i.test(navigator.userAgent); var mac = ios || /Mac/.test(navigator.platform); // Optimize some code when these features are not used var sawReadOnlySpans = false, sawCollapsedSpans = false; // CONSTRUCTOR function CodeMirror(place, options) { if (!(this instanceof CodeMirror)) return new CodeMirror(place, options); this.options = options = options || {}; // Determine effective options based on given values and defaults. for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt)) options[opt] = defaults[opt]; setGuttersForLineNumbers(options); var display = this.display = makeDisplay(place); display.wrapper.CodeMirror = this; updateGutters(this); if (options.autofocus && !mobile) focusInput(this); this.view = makeView(new BranchChunk([new LeafChunk([makeLine("", null, textHeight(display))])])); this.nextOpId = 0; loadMode(this); themeChanged(this); if (options.lineWrapping) this.display.wrapper.className += " CodeMirror-wrap"; // Initialize the content. this.setValue(options.value || ""); // Override magic textarea content restore that IE sometimes does // on our hidden textarea on reload if (ie) setTimeout(bind(resetInput, this, true), 20); this.view.history = makeHistory(); registerEventHandlers(this); // IE throws unspecified error in certain cases, when // trying to access activeElement before onload var hasFocus; try { hasFocus = (document.activeElement == display.input); } catch(e) { } if (hasFocus || (options.autofocus && !mobile)) setTimeout(bind(onFocus, this), 20); else onBlur(this); operation(this, function() { for (var opt in optionHandlers) if (optionHandlers.propertyIsEnumerable(opt)) optionHandlers[opt](this, options[opt], Init); for (var i = 0; i < initHooks.length; ++i) initHooks[i](this); })(); } // DISPLAY CONSTRUCTOR function makeDisplay(place) { var d = {}; var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none;"); input.setAttribute("wrap", "off"); input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); // Wraps and hides input textarea d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); // The actual fake scrollbars. 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"); // DIVs containing the selection and the actual code d.lineDiv = elt("div"); 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("pre", "\u00a0", "CodeMirror-cursor"); // Secondary cursor, shown when on a 'jump' in bi-directional text d.otherCursor = elt("pre", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"); // Used to measure text size d.measure = elt("div", null, "CodeMirror-measure"); // Wraps everything that needs to exist inside the vertically-padded coordinate system d.lineSpace = elt("div", [d.measure, d.selectionDiv, d.lineDiv, d.cursor, d.otherCursor], null, "position: relative; outline: none"); // Moved around its parent to cover visible view d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative"); // Set to the height of the text, causes scrolling d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers d.heightForcer = elt("div", "\u00a0", null, "position: absolute; height: " + scrollerCutOff + "px"); // 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.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"); // 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); // Needed to hide big blue blinking cursor on Mobile Safari if (ios) input.style.width = "0px"; if (!webkit) d.scroller.draggable = true; // Needed to handle Tab key in KHTML if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; } // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). else if (ie_lt8) d.scrollbarH.style.minWidth = d.scrollbarV.style.minWidth = "18px"; // Current visible range (may be bigger than the view window). d.viewOffset = d.showingFrom = d.showingTo = d.lastSizeC = 0; // Used to only resize the line number gutter when necessary (when // the amount of lines crosses a boundary that makes its width change) d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; // See readInput and resetInput d.prevInput = ""; // Set to true when a non-horizontal-scrolling widget is added. As // an optimization, widget aligning is skipped when d is false. d.alignWidgets = false; // Flag that indicates whether we currently expect input to appear // (after some event like 'keypress' or 'input') and are polling // intensively. 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 = []; d.measureLineCachePos = 0; // Tracks when resetInput has punted to just putting a short // string instead of the (large) selection. d.inaccurateSelection = false; // Used to adjust overwrite behaviour when a paste has been // detected d.pasteIncoming = false; return d; } // VIEW CONSTRUCTOR function makeView(doc) { var selPos = {line: 0, ch: 0}; return { doc: doc, // frontier is the point up to which the content has been parsed, frontier: 0, highlight: new Delayed(), sel: {from: selPos, to: selPos, head: selPos, anchor: selPos, shift: false, extend: false}, scrollTop: 0, scrollLeft: 0, overwrite: false, focused: false, // Tracks the maximum line length so that // the horizontal scrollbar can be kept // static when scrolling. maxLine: getLine(doc, 0), maxLineLength: 0, maxLineChanged: false, suppressEdits: false, goalColumn: null, cantEdit: false, keyMaps: [] }; } // STATE UPDATES // Used to get the editor into a consistent state again when options change. function loadMode(cm) { var doc = cm.view.doc; cm.view.mode = CodeMirror.getMode(cm.options, cm.options.mode); doc.iter(0, doc.size, function(line) { line.stateAfter = null; }); cm.view.frontier = 0; startWorker(cm, 100); } function wrappingChanged(cm) { var doc = cm.view.doc, th = textHeight(cm.display); if (cm.options.lineWrapping) { cm.display.wrapper.className += " CodeMirror-wrap"; var perLine = cm.display.scroller.clientWidth / charWidth(cm.display) - 3; doc.iter(0, doc.size, function(line) { if (line.height == 0) return; var guess = Math.ceil(line.text.length / perLine) || 1; if (guess != 1) updateLineHeight(line, guess * th); }); cm.display.sizer.style.minWidth = ""; } else { cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", ""); computeMaxLength(cm.view); doc.iter(0, doc.size, function(line) { if (line.height != 0) updateLineHeight(line, th); }); } regChange(cm, 0, doc.size); clearCaches(cm); setTimeout(function(){updateScrollbars(cm.display, cm.view.doc.height);}, 100); } function keyMapChanged(cm) { var style = keyMap[cm.options.keyMap].style; cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + (style ? " cm-keymap-" + style : ""); } function themeChanged(cm) { cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); clearCaches(cm); } function guttersChanged(cm) { updateGutters(cm); updateDisplay(cm, true); } function updateGutters(cm) { var gutters = cm.display.gutters, specs = cm.options.gutters; removeChildren(gutters); for (var i = 0; i < specs.length; ++i) { var gutterClass = specs[i]; var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)); if (gutterClass == "CodeMirror-linenumbers") { cm.display.lineGutter = gElt; gElt.style.width = (cm.display.lineNumWidth || 1) + "px"; } } gutters.style.display = i ? "" : "none"; } function lineLength(doc, line) { if (line.height == 0) return 0; var len = line.text.length, merged, cur = line; while (merged = collapsedSpanAtStart(cur)) { var found = merged.find(); cur = getLine(doc, found.from.line); len += found.from.ch - found.to.ch; } cur = line; while (merged = collapsedSpanAtEnd(cur)) { var found = merged.find(); len -= cur.text.length - found.from.ch; cur = getLine(doc, found.to.line); len += cur.text.length - found.to.ch; } return len; } function computeMaxLength(view) { view.maxLine = getLine(view.doc, 0); view.maxLineLength = lineLength(view.doc, view.maxLine); view.maxLineChanged = true; view.doc.iter(1, view.doc.size, function(line) { var len = lineLength(view.doc, line); if (len > view.maxLineLength) { view.maxLineLength = len; view.maxLine = line; } }); } // Make sure the gutters options contains the element // "CodeMirror-linenumbers" when the lineNumbers option is true. function setGuttersForLineNumbers(options) { var found = false; for (var i = 0; i < options.gutters.length; ++i) { if (options.gutters[i] == "CodeMirror-linenumbers") { if (options.lineNumbers) found = true; else options.gutters.splice(i--, 1); } } if (!found && options.lineNumbers) options.gutters.push("CodeMirror-linenumbers"); } // SCROLLBARS // Re-synchronize the fake scrollbars with the actual size of the // content. Optionally force a scrollTop. function updateScrollbars(d /* display */, docHeight) { var totalHeight = docHeight + 2 * paddingTop(d); d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px"; var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight); var needsH = d.scroller.scrollWidth > d.scroller.clientWidth; var needsV = scrollHeight > d.scroller.clientHeight; 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 = ""; 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 = ""; 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 (mac_geLion && scrollbarWidth(d.measure) === 0) d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px"; } function visibleLines(display, doc, viewPort) { var top = display.scroller.scrollTop, height = display.wrapper.clientHeight; if (typeof viewPort == "number") top = viewPort; else if (viewPort) {top = viewPort.top; height = viewPort.bottom - viewPort.top;} top = Math.floor(top - paddingTop(display)); var bottom = Math.ceil(top + height); return {from: lineAtHeight(doc, top), to: lineAtHeight(doc, bottom)}; } // LINE NUMBERS function alignHorizontally(cm) { var display = cm.display; if (!display.alignWidgets && !display.gutters.firstChild) return; var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.view.scrollLeft; var gutterW = display.gutters.offsetWidth, l = comp + "px"; for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) { for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l; } display.gutters.style.left = (comp + gutterW) + "px"; } function maybeUpdateLineNumberWidth(cm) { if (!cm.options.lineNumbers) return false; var doc = cm.view.doc, last = lineNumberFor(cm.options, doc.size - 1), display = cm.display; if (last.length != display.lineNumChars) { var test = display.measure.appendChild(elt("div", [elt("div", last)], "CodeMirror-linenumber CodeMirror-gutter-elt")); var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; display.lineGutter.style.width = ""; display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding); display.lineNumWidth = display.lineNumInnerWidth + padding; display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; display.lineGutter.style.width = display.lineNumWidth + "px"; return true; } return false; } function lineNumberFor(options, i) { return String(options.lineNumberFormatter(i + options.firstLineNumber)); } function compensateForHScroll(display) { return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left; } // DISPLAY DRAWING function updateDisplay(cm, changes, viewPort) { var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo; var updated = updateDisplayInner(cm, changes, viewPort); if (updated) { signalLater(cm, cm, "update", cm); if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo) signalLater(cm, cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo); } updateSelection(cm); updateScrollbars(cm.display, cm.view.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) { var display = cm.display, doc = cm.view.doc; if (!display.wrapper.clientWidth) { display.showingFrom = display.showingTo = display.viewOffset = 0; 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 !== true && changes.length == 0 && visible.from > display.showingFrom && visible.to < display.showingTo) return; if (changes && maybeUpdateLineNumberWidth(cm)) changes = true; display.sizer.style.marginLeft = display.scrollbarH.style.left = display.gutters.offsetWidth + "px"; // When merged lines are present, the line that needs to be // redrawn might not be the one that was changed. if (changes !== true && sawCollapsedSpans) for (var i = 0; i < changes.length; ++i) { var ch = changes[i], merged; while (merged = collapsedSpanAtStart(getLine(doc, ch.from))) { var from = merged.find().from.line; if (ch.diff) ch.diff -= ch.from - from; ch.from = from; } } // Used to determine which lines need their line numbers updated var positionsChangedFrom = changes === true ? 0 : Infinity; if (cm.options.lineNumbers && changes && changes !== true) for (var i = 0; i < changes.length; ++i) if (changes[i].diff) { positionsChangedFrom = changes[i].from; break; } var from = Math.max(visible.from - cm.options.viewportMargin, 0); var to = Math.min(doc.size, visible.to + cm.options.viewportMargin); if (display.showingFrom < from && from - display.showingFrom < 20) from = display.showingFrom; if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(doc.size, display.showingTo); if (sawCollapsedSpans) { from = lineNo(visualLine(doc, getLine(doc, from))); while (to < doc.size && lineIsHidden(getLine(doc, to))) ++to; } // Create a range of theoretically intact lines, and punch holes // in that using the change info. var intact = changes === true ? [] : computeIntact([{from: display.showingFrom, to: display.showingTo}], changes); // Clip off the parts that won't be visible var intactLines = 0; for (var i = 0; i < intact.length; ++i) { var range = intact[i]; if (range.from < from) range.from = from; if (range.to > to) range.to = to; 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) return; intact.sort(function(a, b) {return a.from - b.from;}); if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none"; patchDisplay(cm, from, to, intact, positionsChangedFrom); display.lineDiv.style.display = ""; 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; display.showingFrom = from; display.showingTo = to; startWorker(cm, 100); var prevBottom = display.lineDiv.offsetTop; for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) { if (ie_lt8) { var bot = node.offsetTop + node.offsetHeight; height = bot - prevBottom; prevBottom = bot; } else { var box = node.getBoundingClientRect(); height = box.bottom - box.top; } var diff = node.lineObj.height - height; if (height < 2) height = textHeight(display); if (diff > .001 || diff < -.001) updateLineHeight(node.lineObj, height); } display.viewOffset = heightAtLine(cm, getLine(doc, from)); // Position the mover div to align with the current virtual scroll position display.mover.style.top = display.viewOffset + "px"; return true; } function computeIntact(intact, changes) { for (var i = 0, l = changes.length || 0; i < l; ++i) { var change = changes[i], intact2 = [], diff = change.diff || 0; for (var j = 0, l2 = intact.length; j < l2; ++j) { var range = intact[j]; if (change.to <= range.from && change.diff) { intact2.push({from: range.from + diff, to: range.to + diff}); } else if (change.to <= range.from || change.from >= range.to) { intact2.push(range); } else { if (change.from > range.from) intact2.push({from: range.from, to: change.from}); if (change.to < range.to) intact2.push({from: change.to + diff, to: range.to + diff}); } } intact = intact2; } return intact; } function getDimensions(cm) { var d = cm.display, left = {}, width = {}; for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { left[cm.options.gutters[i]] = n.offsetLeft; width[cm.options.gutters[i]] = n.offsetWidth; } return {fixedPos: compensateForHScroll(d), gutterTotalWidth: d.gutters.offsetWidth, gutterLeft: left, gutterWidth: width, wrapperWidth: d.wrapper.clientWidth}; } function patchDisplay(cm, from, to, intact, updateNumbersFrom) { var dims = getDimensions(cm); var display = cm.display, lineNumbers = cm.options.lineNumbers; // IE does bad things to nodes when .innerHTML = "" is used on a parent // we still need widgets and markers intact to add back to the new content later if (!intact.length && !ie && (!webkit || !cm.display.currentWheelTarget)) removeChildren(display.lineDiv); var container = display.lineDiv, cur = container.firstChild; function rm(node) { var next = node.nextSibling; if (webkit && mac && cm.display.currentWheelTarget == node) { node.style.display = "none"; node.lineObj = null; } else { container.removeChild(node); } return next; } var nextIntact = intact.shift(), lineNo = from; cm.view.doc.iter(from, to, function(line) { if (nextIntact && nextIntact.to == lineNo) nextIntact = intact.shift(); if (lineIsHidden(line)) { if (line.height != 0) updateLineHeight(line, 0); } else if (nextIntact && nextIntact.from <= lineNo && nextIntact.to > lineNo) { // This line is intact. Skip to the actual node. Update its // line number if needed. while (cur.lineObj != line) cur = rm(cur); if (lineNumbers && updateNumbersFrom <= lineNo && cur.lineNumber) setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineNo)); cur = cur.nextSibling; } else { // This line needs to be generated. var lineNode = buildLineElement(cm, line, lineNo, dims); container.insertBefore(lineNode, cur); lineNode.lineObj = line; } ++lineNo; }); while (cur) cur = rm(cur); } function buildLineElement(cm, line, lineNo, dims) { var lineElement = lineContent(cm, line); var markers = line.gutterMarkers, display = cm.display; if (!cm.options.lineNumbers && !markers && !line.bgClass && !line.wrapClass && (!line.widgets || !line.widgets.length)) return lineElement; // Lines with gutter elements or a background class need // to be wrapped again, and have the extra elements added // to the wrapper div var wrap = elt("div", null, line.wrapClass, "position: relative"); if (cm.options.lineNumbers || markers) { var gutterWrap = wrap.appendChild(elt("div", null, null, "position: absolute; left: " + dims.fixedPos + "px")); wrap.alignable = [gutterWrap]; if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) wrap.lineNumber = gutterWrap.appendChild( elt("div", lineNumberFor(cm.options, lineNo), "CodeMirror-linenumber CodeMirror-gutter-elt", "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: " + display.lineNumInnerWidth + "px")); if (markers) for (var k = 0; k < cm.options.gutters.length; ++k) { var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]; if (found) gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " + dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px")); } } // Kludge to make sure the styled element lies behind the selection (by z-index) if (line.bgClass) wrap.appendChild(elt("div", "\u00a0", line.bgClass + " CodeMirror-linebackground")); wrap.appendChild(lineElement); if (line.widgets) for (var i = 0, ws = line.widgets; i < ws.length; ++i) { var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); node.widget = widget; if (widget.noHScroll) { (wrap.alignable || (wrap.alignable = [])).push(node); var width = dims.wrapperWidth; node.style.left = dims.fixedPos + "px"; if (!widget.coverGutter) { width -= dims.gutterTotalWidth; node.style.paddingLeft = dims.gutterTotalWidth + "px"; } node.style.width = width + "px"; } if (widget.coverGutter) { node.style.zIndex = 5; node.style.position = "relative"; if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px"; } if (widget.above) wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement); else wrap.appendChild(node); } if (ie_lt8) wrap.style.zIndex = 2; return wrap; } // SELECTION / CURSOR function updateSelection(cm) { var display = cm.display; var collapsed = posEq(cm.view.sel.from, cm.view.sel.to); if (collapsed || cm.options.showCursorWhenSelecting) updateSelectionCursor(cm); else display.cursor.style.display = display.otherCursor.style.display = "none"; if (!collapsed) updateSelectionRange(cm); else display.selectionDiv.style.display = "none"; // Move the hidden textarea near the cursor to prevent scrolling artifacts var headPos = cursorCoords(cm, cm.view.sel.head, "div"); var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect(); 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 function updateSelectionCursor(cm) { var display = cm.display, pos = cursorCoords(cm, cm.view.sel.head, "div"); display.cursor.style.left = pos.left + "px"; display.cursor.style.top = pos.top + "px"; display.cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; display.cursor.style.display = ""; if (pos.other) { display.otherCursor.style.display = ""; display.otherCursor.style.left = pos.other.left + "px"; display.otherCursor.style.top = pos.other.top + "px"; display.otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; } else { display.otherCursor.style.display = "none"; } } // Highlight selection function updateSelectionRange(cm) { var display = cm.display, doc = cm.view.doc, sel = cm.view.sel; var fragment = document.createDocumentFragment(); var clientWidth = display.lineSpace.offsetWidth, pl = paddingLeft(cm.display); function add(left, top, width, bottom) { if (top < 0) top = 0; fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + "px; top: " + top + "px; width: " + (width == null ? clientWidth - left : width) + "px; height: " + (bottom - top) + "px")); } function drawForLine(line, fromArg, toArg, retTop) { var lineObj = getLine(doc, line); var lineLen = lineObj.text.length, rVal = retTop ? Infinity : -Infinity; function coords(ch) { return charCoords(cm, {line: line, ch: ch}, "div", lineObj); } 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; 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 (left < pl + 1) left = pl; add(left, rightPos.top, right - left, rightPos.bottom); }); return rVal; } 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; } 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); } } removeChildrenAndAdd(display.selectionDiv, fragment); display.selectionDiv.style.display = ""; } // Cursor-blinking function restartBlink(cm) { 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); } // HIGHLIGHT WORKER function startWorker(cm, time) { if (cm.view.frontier < cm.display.showingTo) cm.view.highlight.set(time, bind(highlightWorker, cm)); } function highlightWorker(cm) { var view = cm.view, doc = view.doc; if (view.frontier >= cm.display.showingTo) return; var end = +new Date + cm.options.workTime; var state = copyState(view.mode, getStateBefore(cm, view.frontier)); var changed = [], prevChange; doc.iter(view.frontier, Math.min(doc.size, cm.display.showingTo + 500), function(line) { if (view.frontier >= cm.display.showingFrom) { // Visible if (highlightLine(cm, line, state) && view.frontier >= cm.display.showingFrom) { if (prevChange && prevChange.end == view.frontier) prevChange.end++; else changed.push(prevChange = {start: view.frontier, end: view.frontier + 1}); } line.stateAfter = copyState(view.mode, state); } else { processLine(cm, line, state); line.stateAfter = view.frontier % 5 == 0 ? copyState(view.mode, state) : null; } ++view.frontier; if (+new Date > end) { startWorker(cm, cm.options.workDelay); return true; } }); if (changed.length) operation(cm, function() { for (var i = 0; i < changed.length; ++i) regChange(this, changed[i].start, changed[i].end); })(); } // Finds the line to start with when starting a parse. Tries to // find a line with a stateAfter, so that it can start with a // 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.view.doc; for (var search = n, lim = n - 100; search > lim; --search) { if (search == 0) return 0; var line = getLine(doc, search-1); if (line.stateAfter) return search; var indented = countColumn(line.text, null, cm.options.tabSize); if (minline == null || minindent > indented) { minline = search - 1; minindent = indented; } } return minline; } function getStateBefore(cm, n) { var view = cm.view; var pos = findStartLine(cm, n), state = pos && getLine(view.doc, pos-1).stateAfter; if (!state) state = startState(view.mode); else state = copyState(view.mode, state); view.doc.iter(pos, n, function(line) { processLine(cm, line, state); var save = pos == n - 1 || pos % 5 == 0 || pos >= view.showingFrom && pos < view.showingTo; line.stateAfter = save ? copyState(view.mode, state) : null; ++pos; }); return state; } // POSITION MEASUREMENT function paddingTop(display) {return display.lineSpace.offsetTop;} function paddingLeft(display) { var e = removeChildrenAndAdd(display.measure, elt("pre")).appendChild(elt("span", "x")); return e.offsetLeft; } function measureChar(cm, line, ch, data) { var data = data || measureLine(cm, line), dir = -1; for (var pos = ch;; pos += dir) { var r = data[pos]; if (r) break; if (dir < 0 && pos == 0) dir = 1; } return {left: pos < ch ? r.right : r.left, right: pos > ch ? r.left : r.right, top: r.top, bottom: r.bottom}; } function measureLine(cm, line) { // First look in the cache var display = cm.display, cache = cm.display.measureLineCache; for (var i = 0; i < cache.length; ++i) { var memo = cache[i]; if (memo.text == line.text && memo.markedSpans == line.markedSpans && display.scroller.clientWidth == memo.width) return memo.measure; } var measure = measureLineInner(cm, line); // Store result in the cache var memo = {text: line.text, width: display.scroller.clientWidth, markedSpans: line.markedSpans, measure: measure}; if (cache.length == 16) cache[++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); // IE does not cache element positions of inline elements between // calls to getBoundingClientRect. This makes the loop below, // which gathers the positions of all the characters on the line, // do an amount of layout work quadratic to the number of // characters. When line wrapping is off, we try to improve things // by first subdividing the line into a bunch of inline blocks, so // that IE can reuse most of the layout information from caches // for those blocks. This does interfere with line wrapping, so it // doesn't work when wrapping is on, but in that case the // situation is slightly better, since IE does cache line-wrapping // information and only recomputes per-line. if (ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) { var fragment = document.createDocumentFragment(); var chunk = 10, n = pre.childNodes.length; for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) { var wrap = elt("div", null, null, "display: inline-block"); for (var j = 0; j < chunk && n; ++j) { wrap.appendChild(pre.firstChild); --n; } fragment.appendChild(wrap); } pre.appendChild(fragment); } removeChildrenAndAdd(display.measure, pre); var outer = display.lineDiv.getBoundingClientRect(); var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight; for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) { var size = cur.getBoundingClientRect(); 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]; 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); break; } } if (j == vranges.length) vranges.push(top, bot); data[i] = {left: size.left - outer.left, right: size.right - outer.left, top: j}; } 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]; } return data; } function clearCaches(cm) { cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0; cm.display.cachedCharWidth = cm.display.cachedTextHeight = null; cm.view.maxLineChanged = true; } // 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) { var size = lineObj.widgets[i].node.offsetHeight; rect.top += size; rect.bottom += size; } if (context == "line") return rect; if (!context) context = "local"; var yOff = heightAtLine(cm, lineObj); if (context != "local") yOff -= cm.display.viewOffset; if (context == "page") { var lOff = cm.display.lineSpace.getBoundingClientRect(); yOff += lOff.top + (window.pageYOffset || (document.documentElement || document.body).scrollTop); var xOff = lOff.left + (window.pageXOffset || (document.documentElement || document.body).scrollLeft); rect.left += xOff; rect.right += xOff; } rect.top += yOff; rect.bottom += yOff; return rect; } function charCoords(cm, pos, context, lineObj) { if (!lineObj) lineObj = getLine(cm.view.doc, pos.line); return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch), context); } function cursorCoords(cm, pos, context, lineObj, measurement) { lineObj = lineObj || getLine(cm.view.doc, pos.line); if (!measurement) measurement = measureLine(cm, lineObj); function get(ch, right) { var m = measureChar(cm, lineObj, ch, measurement); if (right) m.left = m.right; else m.right = m.left; return intoCoordSystem(cm, lineObj, m, context); } 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) { // Opera and IE return 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; } // Coords must be lineSpace-local function coordsChar(cm, x, y) { var doc = cm.view.doc; y += cm.display.viewOffset; if (y < 0) return {line: 0, ch: 0, outside: true}; var lineNo = lineAtHeight(doc, y); if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc, doc.size - 1).text.length}; if (x < 0) x = 0; for (;;) { var lineObj = getLine(doc, lineNo); var found = coordsCharInner(cm, lineObj, lineNo, x, y); var merged = collapsedSpanAtEnd(lineObj); if (merged && found.ch == lineRight(lineObj)) lineNo = merged.find().to.line; else return found; } } function coordsCharInner(cm, lineObj, lineNo, x, y) { var innerOff = y - heightAtLine(cm, lineObj); var wrongLine = false, cWidth = cm.display.wrapper.clientWidth; var measurement = measureLine(cm, lineObj); function getX(ch) { var sp = cursorCoords(cm, {line: lineNo, ch: ch}, "line", lineObj, measurement); wrongLine = true; if (innerOff > sp.bottom) return Math.max(0, sp.left - cWidth); else if (innerOff < sp.top) return sp.left + cWidth; else wrongLine = false; return sp.left; } var bidi = getOrder(lineObj), dist = lineObj.text.length; var from = lineLeft(lineObj), to = lineRight(lineObj); var fromX = paddingLeft(cm.display), toX = getX(to); if (x > toX) return {line: lineNo, ch: to, outside: wrongLine}; // 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; while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch; return {line: lineNo, ch: ch, after: after, outside: wrongLine}; } var step = Math.ceil(dist / 2), middle = from + step; if (bidi) { middle = from; for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1); } var middleX = getX(middle); if (middleX > x) {to = middle; toX = middleX; if (wrongLine) toX += 1000; dist -= step;} else {from = middle; fromX = middleX; dist = step;} } } var measureText; function textHeight(display) { if (display.cachedTextHeight != null) return display.cachedTextHeight; if (measureText == null) { measureText = elt("pre"); // Measure a bunch of lines, for browsers that compute // fractional heights. for (var i = 0; i < 49; ++i) { measureText.appendChild(document.createTextNode("x")); measureText.appendChild(elt("br")); } measureText.appendChild(document.createTextNode("x")); } removeChildrenAndAdd(display.measure, measureText); var height = measureText.offsetHeight / 50; if (height > 3) display.cachedTextHeight = height; removeChildren(display.measure); return height || 1; } function charWidth(display) { if (display.cachedCharWidth != null) return display.cachedCharWidth; var anchor = elt("span", "x"); var pre = elt("pre", [anchor]); removeChildrenAndAdd(display.measure, pre); var width = anchor.offsetWidth; if (width > 2) display.cachedCharWidth = width; return width || 10; } // OPERATIONS // Operations are used to wrap changes in such a way that each // change won't have to update the cursor and display (which would // be awkward, slow, and error-prone), but instead updates are // batched and then all combined and executed at once. function startOperation(cm) { if (cm.curOp) ++cm.curOp.depth; else cm.curOp = { // Nested operations delay update until the outermost one // finishes. depth: 1, // An array of ranges of lines that have to be updated. See // updateDisplay. changes: [], delayedCallbacks: [], updateInput: null, userSelChange: null, textChanged: null, selectionChanged: false, updateMaxLine: false, id: ++cm.nextOpId }; } function endOperation(cm) { var op = cm.curOp; if (--op.depth) return; cm.curOp = null; var view = cm.view, display = cm.display; if (op.updateMaxLine) computeMaxLength(view); if (view.maxLineChanged && !cm.options.lineWrapping) { var width = measureChar(cm, view.maxLine, view.maxLine.text.length).right; display.sizer.style.minWidth = (width + 3 + scrollerCutOff) + "px"; view.maxLineChanged = false; } var newScrollPos, updated; if (op.selectionChanged) { var coords = cursorCoords(cm, view.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 (!updated && op.selectionChanged) updateSelection(cm); if (newScrollPos) scrollCursorIntoView(cm); if (op.selectionChanged) restartBlink(cm); if (view.focused && op.updateInput) resetInput(cm, op.userSelChange); if (op.textChanged) signal(cm, "change", cm, op.textChanged); if (op.selectionChanged) signal(cm, "cursorActivity", cm); for (var i = 0; i < op.delayedCallbacks.length; ++i) op.delayedCallbacks[i](cm); } // Wraps a function in an operation. Returns the wrapped function. function operation(cm1, f) { return function() { var cm = cm1 || this; startOperation(cm); try {var result = f.apply(cm, arguments);} finally {endOperation(cm);} return result; }; } function regChange(cm, from, to, lendiff) { cm.curOp.changes.push({from: from, to: to, diff: lendiff}); } // INPUT HANDLING function slowPoll(cm) { if (cm.view.pollingFast) return; cm.display.poll.set(cm.options.pollInterval, function() { readInput(cm); if (cm.view.focused) slowPoll(cm); }); } function fastPoll(cm) { var missed = false; cm.display.pollingFast = true; function p() { var changed = readInput(cm); if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);} else {cm.display.pollingFast = false; slowPoll(cm);} } cm.display.poll.set(20, p); } // prevInput is a hack to work with IME. If we reset the textarea // on every change, that breaks IME. So we look for changes // compared to the previous content instead. (Modern browsers have // events that indicate IME taking place, but these are not widely // supported or compatible enough yet to rely on.) function readInput(cm) { var input = cm.display.input, prevInput = cm.display.prevInput, view = cm.view, sel = view.sel; if (!view.focused || hasSelection(input) || isReadOnly(cm)) return false; var text = input.value; if (text == prevInput && posEq(sel.from, sel.to)) return false; startOperation(cm); view.sel.shift = false; var same = 0, l = Math.min(prevInput.length, text.length); while (same < l && prevInput[same] == text[same]) ++same; var from = sel.from, to = sel.to; if (same < prevInput.length) from = {line: from.line, ch: from.ch - (prevInput.length - same)}; else if (view.overwrite && posEq(from, to) && !cm.display.pasteIncoming) to = {line: to.line, ch: Math.min(getLine(cm.view.doc, to.line).text.length, to.ch + (text.length - same))}; var updateInput = cm.curOp.updateInput; updateDoc(cm, from, to, splitLines(text.slice(same)), "end", cm.display.pasteIncoming ? "paste" : "input", {from: from, to: to}); cm.curOp.updateInput = updateInput; if