UNPKG

alpaca

Version:

Alpaca provides the easiest and fastest way to generate interactive forms for the web and mobile devices. It runs simply as HTML5 or more elaborately using Bootstrap, jQuery Mobile or jQuery UI. Alpaca uses Handlebars to process JSON schema and provide

1,194 lines (1,075 loc) 214 kB
// 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 = ie && (document.documentMode == null || document.documentMode < 8); var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9); 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|Opera Mobi|IEMobile/i.test(navigator.userAgent); var mac = ios || /Mac/.test(navigator.platform); var windows = /windows/i.test(navigator.platform); var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/); if (opera_version) opera_version = Number(opera_version[1]); // 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); // 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 docStart = typeof options.value == "string" ? 0 : options.value.first; var display = this.display = makeDisplay(place, docStart); display.wrapper.CodeMirror = this; updateGutters(this); if (options.autofocus && !mobile) focusInput(this); this.state = {keyMaps: [], overlays: [], modeGen: 0, overwrite: false, focused: false, suppressEdits: false, pasteIncoming: false, draggingText: false, highlight: new Delayed()}; themeChanged(this); if (options.lineWrapping) this.display.wrapper.className += " CodeMirror-wrap"; var doc = options.value; if (typeof doc == "string") doc = new Doc(options.value, options.mode); operation(this, attachDoc)(this, doc); // Override magic textarea content restore that IE sometimes does // on our hidden textarea on reload if (ie) setTimeout(bind(resetInput, this, true), 20); 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, docStart) { var d = {}; var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none;"); 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"); // 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("div", "\u00a0", "CodeMirror-cursor"); // Secondary cursor, shown when on a 'jump' in bi-directional text d.otherCursor = elt("div", "\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", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;"); // 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.lastSizeC = 0; d.showingFrom = d.showingTo = docStart; // 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; // Tracks the maximum line length so that the horizontal scrollbar // can be kept static when scrolling. d.maxLine = null; d.maxLineLength = 0; d.maxLineChanged = false; // Used for measuring wheel scrolling granularity d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; return d; } // STATE UPDATES // Used to get the editor into a consistent state again when options change. function loadMode(cm) { cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption); cm.doc.iter(function(line) { if (line.stateAfter) line.stateAfter = null; if (line.styles) line.styles = null; }); cm.doc.frontier = cm.doc.first; startWorker(cm, 100); cm.state.modeGen++; if (cm.curOp) regChange(cm); } function wrappingChanged(cm) { if (cm.options.lineWrapping) { cm.display.wrapper.className += " CodeMirror-wrap"; cm.display.sizer.style.minWidth = ""; } else { cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", ""); computeMaxLength(cm); } estimateLineHeights(cm); regChange(cm); clearCaches(cm); setTimeout(function(){updateScrollbars(cm.display, cm.doc.height);}, 100); } function estimateHeight(cm) { var th = textHeight(cm.display), wrapping = cm.options.lineWrapping; var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3); return function(line) { if (lineIsHidden(cm.doc, line)) return 0; else if (wrapping) return (Math.ceil(line.text.length / perLine) || 1) * th; else return th; }; } function estimateLineHeights(cm) { var doc = cm.doc, est = estimateHeight(cm); doc.iter(function(line) { var estHeight = est(line); if (estHeight != line.height) updateLineHeight(line, estHeight); }); } 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); regChange(cm); } 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(cm) { var d = cm.display, doc = cm.doc; d.maxLine = getLine(doc, doc.first); d.maxLineLength = lineLength(doc, d.maxLine); d.maxLineChanged = true; doc.iter(function(line) { var len = lineLength(doc, line); if (len > d.maxLineLength) { d.maxLineLength = len; d.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 + paddingVert(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 || !cm.options.fixedGutter)) return; var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.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; } if (cm.options.fixedGutter) display.gutters.style.left = (comp + gutterW) + "px"; } function maybeUpdateLineNumberWidth(cm) { if (!cm.options.lineNumbers) return false; var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + 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 getRect(display.scroller).left - getRect(display.sizer).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, "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) { var display = cm.display, doc = cm.doc; if (!display.wrapper.clientWidth) { display.showingFrom = display.showingTo = doc.first; 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.length == 0 && visible.from > display.showingFrom && visible.to < display.showingTo) return; if (maybeUpdateLineNumberWidth(cm)) changes = [{from: doc.first, to: doc.first + doc.size}]; var gutterW = display.sizer.style.marginLeft = display.gutters.offsetWidth + "px"; display.scrollbarH.style.left = cm.options.fixedGutter ? gutterW : "0"; // Used to determine which lines need their line numbers updated var positionsChangedFrom = Infinity; if (cm.options.lineNumbers) for (var i = 0; i < changes.length; ++i) if (changes[i].diff) { positionsChangedFrom = changes[i].from; break; } var end = doc.first + doc.size; var from = Math.max(visible.from - cm.options.viewportMargin, doc.first); var to = Math.min(end, visible.to + cm.options.viewportMargin); if (display.showingFrom < from && from - display.showingFrom < 20) from = Math.max(doc.first, display.showingFrom); if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(end, display.showingTo); if (sawCollapsedSpans) { from = lineNo(visualLine(doc, getLine(doc, from))); while (to < end && lineIsHidden(doc, getLine(doc, to))) ++to; } // Create a range of theoretically intact lines, and punch holes // in that using the change info. var intact = [{from: Math.max(display.showingFrom, doc.first), to: Math.min(display.showingTo, end)}]; if (intact[0].from >= intact[0].to) intact = []; else intact = computeIntact(intact, changes); // When merged lines are present, we might have to reduce the // intact ranges because changes in continued fragments of the // intact lines do require the lines to be redrawn. if (sawCollapsedSpans) for (var i = 0; i < intact.length; ++i) { var range = intact[i], merged; while (merged = collapsedSpanAtEnd(getLine(doc, range.to - 1))) { var newTo = merged.find().from.line; if (newTo > range.from) range.to = newTo; else { intact.splice(i--, 1); break; } } } // 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) { updateViewOffset(cm); return; } intact.sort(function(a, b) {return a.from - b.from;}); var focused = document.activeElement; 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(); 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 = getRect(node); 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); var widgets = node.lineObj.widgets; if (widgets) for (var i = 0; i < widgets.length; ++i) widgets[i].height = widgets[i].node.offsetHeight; } } updateViewOffset(cm); if (visibleLines(display, doc, viewPort).to > to) updateDisplayInner(cm, [], viewPort); return true; } function updateViewOffset(cm) { var off = cm.display.viewOffset = heightAtLine(cm, getLine(cm.doc, cm.display.showingFrom)); // Position the mover div to align with the current virtual scroll position cm.display.mover.style.top = off + "px"; } 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; if (!intact.length && (!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 { node.parentNode.removeChild(node); } return next; } var nextIntact = intact.shift(), lineN = from; cm.doc.iter(from, to, function(line) { 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) { var prev = cur.previousSibling; if (/pre/i.test(prev.nodeName)) { var wrap = elt("div", null, null, "position: relative"); prev.parentNode.replaceChild(wrap, prev); wrap.appendChild(prev); prev = wrap; } var wnode = prev.appendChild(elt("div", [line.widgets[i].node], "CodeMirror-linewidget")); positionLineWidget(line.widgets[i], 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. while (cur.lineObj != line) cur = rm(cur); if (lineNumbers && updateNumbersFrom <= lineN && cur.lineNumber) setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineN)); cur = cur.nextSibling; } else { // For lines with widgets, make an attempt to find and reuse // the existing element, so that widgets aren't needlessly // removed and re-inserted into the dom if (line.widgets) for (var j = 0, search = cur, reuse; search && j < 20; ++j, search = search.nextSibling) if (search.lineObj == line && /div/i.test(search.nodeName)) { reuse = search; break; } // This line needs to be generated. var lineNode = buildLineElement(cm, line, lineN, dims, reuse); if (lineNode != reuse) { container.insertBefore(lineNode, cur); } else { while (cur != reuse) cur = rm(cur); cur = cur.nextSibling; } lineNode.lineObj = line; } ++lineN; }); while (cur) cur = rm(cur); } function buildLineElement(cm, line, lineNo, dims, reuse) { var lineElement = lineContent(cm, line); var markers = line.gutterMarkers, display = cm.display, wrap; if (!cm.options.lineNumbers && !markers && !line.bgClass && !line.wrapClass && !line.widgets) return lineElement; // Lines with gutter elements, widgets or a background class need // to be wrapped again, and have the extra elements added to the // wrapper div if (reuse) { reuse.alignable = null; var isOk = true, widgetsSeen = 0; 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; } if (widget.node == n.firstChild) { positionLineWidget(widget, n, reuse, dims); ++widgetsSeen; if (isFirst) reuse.insertBefore(lineElement, n); break; } } if (i == line.widgets.length) { isOk = false; break; } } } if (isOk && widgetsSeen == line.widgets.length) { wrap = reuse; reuse.className = line.wrapClass || ""; } } if (!wrap) { wrap = elt("div", null, line.wrapClass, "position: relative"); wrap.appendChild(lineElement); } // Kludge to make sure the styled element lies behind the selection (by z-index) if (line.bgClass) wrap.insertBefore(elt("div", null, line.bgClass + " CodeMirror-linebackground"), wrap.firstChild); if (cm.options.lineNumbers || markers) { var gutterWrap = wrap.insertBefore(elt("div", null, null, "position: absolute; left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"), wrap.firstChild); if (cm.options.fixedGutter) (wrap.alignable || (wrap.alignable = [])).push(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")); } } 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"); positionLineWidget(widget, node, wrap, dims); if (widget.above) wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement); else wrap.appendChild(node); signalLater(widget, "redraw"); } return wrap; } function positionLineWidget(widget, node, wrap, dims) { 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"; } } // SELECTION / CURSOR function updateSelection(cm) { var display = cm.display; var collapsed = posEq(cm.doc.sel.from, cm.doc.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.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 function updateSelectionCursor(cm) { var display = cm.display, pos = cursorCoords(cm, cm.doc.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.doc, sel = cm.doc.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, Pos(line, 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.doc.mode.startState && cm.doc.frontier < cm.display.showingTo) cm.state.highlight.set(time, bind(highlightWorker, cm)); } function highlightWorker(cm) { var doc = cm.doc; if (doc.frontier < doc.first) doc.frontier = doc.first; if (doc.frontier >= cm.display.showingTo) return; var end = +new Date + cm.options.workTime; var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)); var changed = [], prevChange; doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.showingTo + 500), function(line) { if (doc.frontier >= cm.display.showingFrom) { // Visible var oldStyles = line.styles; line.styles = highlightLine(cm, line, state); var ischange = !oldStyles || oldStyles.length != line.styles.length; for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i]; if (ischange) { if (prevChange && prevChange.end == doc.frontier) prevChange.end++; else changed.push(prevChange = {start: doc.frontier, end: doc.frontier + 1}); } line.stateAfter = copyState(doc.mode, state); } else { processLine(cm, line, state); line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null; } ++doc.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.doc; for (var search = n, lim = n - 100; search > lim; --search) { if (search <= doc.first) return doc.first; 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 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 (!state) state = startState(doc.mode); else state = copyState(doc.mode, state); doc.iter(pos, n, function(line) { processLine(cm, line, state); var save = pos == n - 1 || pos % 5 == 0 || pos >= display.showingFrom && pos < display.showingTo; line.stateAfter = save ? copyState(doc.mode, state) : null; ++pos; }); return state; } // POSITION MEASUREMENT function paddingTop(display) {return display.lineSpace.offsetTop;} function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;} function paddingLeft(display) { var e = removeChildrenAndAdd(display.measure, elt("pre", null, null, "text-align: left")).appendChild(elt("span", "x")); return e.offsetLeft; } function measureChar(cm, line, ch, data) { var dir = -1; data = data || measureLine(cm, line); 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 findCachedMeasurement(cm, line) { var 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 && cm.display.scroller.clientWidth == memo.width && memo.classes == line.textClass + "|" + line.bgClass + "|" + line.wrapClass) return memo.measure; } } 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); } 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 = getRect(display.lineDiv); var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight; // Work around an IE7/8 bug where it will sometimes have randomly // replaced our pre with a clone at this point. 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]; 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); 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}; } 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 measureLineWidth(cm, line) { var hasBadSpan = false; if (line.markedSpans) for (var i = 0; i < line.markedSpans; ++i) { var sp = line.markedSpans[i]; 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; var pre = lineContent(cm, line); var end = pre.appendChild(zeroWidthElement(cm.display.measure)); removeChildrenAndAdd(cm.display.measure, pre); return getRect(end).right - getRect(cm.display.lineDiv).left; } function clearCaches(cm) { cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0; cm.display.cachedCharWidth = cm.display.cachedTextHeight = null; cm.display.maxLineChanged = true; cm.display.lineNumChars = null; } // 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 = widgetHeight(lineObj.widgets[i]); 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 = 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); rect.left += xOff; rect.right += xOff; } rect.top += yOff; rect.bottom += yOff; return rect; } // Context may be "window", "page", "div", or "local"/null // Result is in local coords function fromCoordSystem(cm, coords, context) { if (context == "div") return coords; var left = coords.left, top = coords.top; if (context == "page") { left -= window.pageXOffset || (document.documentElement || document.body).scrollLeft; top -= window.pageYOffset || (document.documentElement || document.body).scrollTop; } 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}; } function charCoords(cm, pos, context, lineObj) { if (!lineObj) lineObj = getLine(cm.doc, pos.line); return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch), 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); 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) { // 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; } function PosMaybeOutside(line, ch, outside) { var pos = new Pos(line, ch); if (outside) pos.outside = true; return pos; } // Coords must be lineSpace-local function coordsChar(cm, x, y) { var doc = cm.doc; y += cm.display.viewOffset; if (y < 0) return PosMaybeOutside(doc.first, 0, true); 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); if (x < 0) x = 0; for (;;) { var lineObj = getLine(doc, lineNo); var found = coordsCharInner(cm, lineObj, lineNo, x, y); var merged = collapsedSpanAtEnd(lineObj); var mergedPos = merged && merged.find(); if (merged && found.ch >= mergedPos.from.ch) lineNo = mergedPos.to.line; else return found; } } function coordsCharInner(cm, lineObj, lineNo, x, y) { var innerOff = y - heightAtLine(cm, lineObj); var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth; var measurement = measureLine(cm, lineObj); function getX(ch) { var sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, measurement); wrongLine = true; if (innerOff > sp.bottom) return sp.left - adjust; else if (innerOff < sp.top) return sp.left + adjust; else wrongLine = false; return sp.left; } var bidi = getOrder(lineObj), dist = lineObj.