UNPKG

liberty-prettydiff

Version:

Language aware code comparison tool for several web based languages. It also beautifies, minifies, and a few other things.

1,273 lines (1,167 loc) 579 kB
// CodeMirror, copyright (c) by Marijn Haverbeke and others Distributed under an // MIT license: http://codemirror.net/LICENSE This is CodeMirror // (http://codemirror.net), a code editor implemented in JavaScript on top of // the browser's DOM. // // You can find some technical background for some of the code below at // http://marijnhaverbeke.nl/blog/#cm-internals . (function (mod) { if (typeof exports == "object" && typeof module == "object") { // CommonJS module.exports = mod(); } else if (typeof define == "function" && define.amd) { // AMD return define([], mod); } else { // Plain browser env this.codeMirror = mod(); } })(function () { "use strict"; // BROWSER SNIFFING Kludges for bugs and behavior differences that can't be // feature detected are enabled based on userAgent etc sniffing. var gecko = /gecko\/\d/i.test(navigator.userAgent); // ie_uptoN means Internet Explorer version N or lower var ie_upto10 = /MSIE \d/.test(navigator.userAgent); var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent); var ie = ie_upto10 || ie_11up; var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]); var webkit = /WebKit\//.test(navigator.userAgent); var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent); var chrome = /Chrome\//.test(navigator.userAgent); var presto = /Opera\//.test(navigator.userAgent); var safari = /Apple Computer/.test(navigator.vendor); var khtml = /KHTML\//.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 = /win/i.test(navigator.platform); var presto_version = presto && navigator .userAgent .match(/Version\/(\d*\.\d*)/); if (presto_version) { presto_version = Number(presto_version[1]); } if (presto_version && presto_version >= 15) { presto = false; webkit = true; } // Some browsers use the wrong event properties to signal cmd/ctrl on OS X var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)); var captureRightClick = gecko || (ie && ie_version >= 9); // Optimize some code when these features are not used. var sawReadOnlySpans = false, sawCollapsedSpans = false; // EDITOR CONSTRUCTOR A CodeMirror instance represents an editor. This is the // object that user code is usually dealing with. function codeMirror(place, options) { if (!(this instanceof codeMirror)) { return new codeMirror(place, options); } this.options = options = options ? copyObj(options) : {}; // Determine effective options based on given values and defaults. copyObj(defaults, options, false); setGuttersForLineNumbers(options); var doc = options.value; if (typeof doc == "string") { doc = new Doc(doc, options.mode); } this.doc = doc; var display = this.display = new Display(place, doc); display.wrapper.CodeMirror = this; updateGutters(this); themeChanged(this); if (options.lineWrapping) { this.display.wrapper.className += " CodeMirror-wrap"; } if (options.autofocus && !mobile) { focusInput(this); } initScrollbars(this); this.state = { cutIncoming : false, // help recognize paste/cut edits in readInput draggingText : false, focused : false, highlight : new Delayed(), // stores highlight worker timeout keyMaps : [], // stores maps added by addKeyMap keySeq : null, // Unfinished key sequence modeGen : 0, // bumped when mode/overlay changes, used to invalidate highlighting info overlays : [], // highlighting overlays, as added by addOverlay overwrite : false, pasteIncoming: false, suppressEdits: false // used to disable editing during key handlers when in readOnly mode }; // Override magic textarea content restore that IE sometimes does on our hidden // textarea on reload if (ie && ie_version < 11) { setTimeout(bind(resetInput, this, true), 20); } registerEventHandlers(this); ensureGlobalHandlers(); startOperation(this); this.curOp.forceUpdate = true; attachDoc(this, doc); if ((options.autofocus && !mobile) || activeElt() == display.input) { setTimeout(bind(onFocus, this), 20); } else { onBlur(this); } for (var opt in optionHandlers) { if (optionHandlers.hasOwnProperty(opt)) { optionHandlers[opt](this, options[opt], Init); } } maybeUpdateLineNumberWidth(this); for (var i = 0; i < initHooks.length; i += 1) { initHooks[i](this); } endOperation(this); // Suppress optimizelegibility in Webkit, since it breaks text measuring on line // wrapping boundaries. if (webkit && options.lineWrapping && getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") { display.lineDiv.style.textRendering = "auto"; } } // DISPLAY CONSTRUCTOR The display handles the DOM integration, both for input // reading and content drawing. It holds references to DOM nodes and // display-related state. function Display(place, doc) { var d = this; // The semihidden textarea that is focused when the editor is focused, and // receives input. var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none"); // The textarea is kept positioned near the cursor to prevent the fact that // it'll be scrolled into view on input from scrolling our fake cursor out of // view. On webkit, when wrap=off, paste is very slow. So make the area wide // instead. 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("spellcheck", "false"); // Wraps and hides input textarea d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); // Covers bottom-right square when both scrollbars are present. d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); d .scrollbarFiller .setAttribute("not-content", "true"); // Covers bottom of gutter when coverGutterNextToScrollbar is on and h scrollbar // is present. d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); d .gutterFiller .setAttribute("not-content", "true"); // Will contain the actual code, positioned to cover the viewport. d.lineDiv = elt("div", null, "CodeMirror-code"); // Elements are added to these to represent selection and cursors. d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); d.cursorDiv = elt("div", null, "CodeMirror-cursors"); // A visibility: hidden element used to find the size of things. d.measure = elt("div", null, "CodeMirror-measure"); // When lines outside of the viewport are measured, they are drawn in this. d.lineMeasure = elt("div", null, "CodeMirror-measure"); // Wraps everything that needs to exist inside the vertically-padded coordinate // system d.lineSpace = elt("div", [ d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv ], 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 document, allowing scrolling. d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); d.sizerWidth = null; // Behavior of elts with overflow: auto and padding is inconsistent across // browsers. This is used to ensure the scrollable area is big enough. d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;"); // Will contain the gutters, if any. d.gutters = elt("div", null, "CodeMirror-gutters"); d.lineGutter = null; // Actual scrollable element. d.scroller = elt("div", [ d.sizer, d.heightForcer, d.gutters ], "CodeMirror-scroll"); d .scroller .setAttribute("tabIndex", "-1"); // The element in which the editor lives. d.wrapper = elt("div", [ d.inputDiv, d.scrollbarFiller, d.gutterFiller, d.scroller ], "CodeMirror"); // Work around IE7 z-index bug (not perfect, hence IE7 not really being // supported) if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } // 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"; } if (place) { if (place.appendChild) { place.appendChild(d.wrapper); } else { place(d.wrapper); } } // Current rendered range (may be bigger than the view window). d.viewFrom = d.viewTo = doc.first; d.reportedViewFrom = d.reportedViewTo = doc.first; // Information about the rendered lines. d.view = []; d.renderedView = null; // Holds info about a single rendered line when it was rendered for measurement, // while not in view. d.externalMeasured = null; // Empty space (in pixels) above the view d.viewOffset = 0; d.lastWrapHeight = d.lastWrapWidth = 0; d.updateLineNumbers = null; d.nativeBarWidth = d.barHeight = d.barWidth = 0; d.scrollbarsClipped = false; // Used to only resize the line number gutter when necessary (when the amount of // lines crosses a boundary that makes its width change) d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; // See readInput and resetInput d.prevInput = ""; // Set to true when a non-horizontal-scrolling line widget is added. As an // optimization, line widget aligning is skipped when this is false. d.alignWidgets = false; // Flag that indicates whether we expect input to appear real soon now (after // some event like 'keypress' or 'input') and are polling intensively. d.pollingFast = false; // Self-resetting timeout for the poller d.poll = new Delayed(); d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; // Tracks when resetInput has punted to just putting a short string into the // textarea instead of the full 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; // True when shift is held down. d.shift = false; // Used to track whether anything happened since the context menu was opened. d.selForContextMenu = null; } // 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); resetModeState(cm); } function resetModeState(cm) { 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 += 1; if (cm.curOp) { regChange(cm); } } function wrappingChanged(cm) { if (cm.options.lineWrapping) { addClass(cm.display.wrapper, "CodeMirror-wrap"); cm.display.sizer.style.minWidth = ""; cm.display.sizerWidth = null; } else { rmClass(cm.display.wrapper, "CodeMirror-wrap"); findMaxLine(cm); } estimateLineHeights(cm); regChange(cm); clearCaches(cm); setTimeout(function () { updateScrollbars(cm); }, 100); } // Returns a function that estimates the height of a line, to use as first // approximation until the line becomes visible (and is thus properly // measurable). 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; } var widgetsHeight = 0; if (line.widgets) { for (var i = 0; i < line.widgets.length; i += 1) { if (line.widgets[i].height) { widgetsHeight += line .widgets[i] .height; } } if (wrapping) { return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th; } else { return widgetsHeight + 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 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); setTimeout(function () { alignHorizontally(cm); }, 20); } // Rebuild the gutter elements, ensure the margin to the left of the code // matches their width. function updateGutters(cm) { var gutters = cm.display.gutters, specs = cm.options.gutters; removeChildren(gutters); for (var i = 0; i < specs.length; i += 1) { 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"; updateGutterSpace(cm); } function updateGutterSpace(cm) { var width = cm.display.gutters.offsetWidth; cm.display.sizer.style.marginLeft = width + "px"; } // Compute the character length of a line, taking into account collapsed ranges // (see markText) that might hide parts, and join other lines onto it. function lineLength(line) { if (line.height == 0) { return 0; } var len = line.text.length, merged, cur = line; while (merged = collapsedSpanAtStart(cur)) { var found = merged.find(0, true); cur = found.from.line; len += found.from.ch - found.to.ch; } cur = line; while (merged = collapsedSpanAtEnd(cur)) { var found = merged.find(0, true); len -= cur.text.length - found.from.ch; cur = found.to.line; len += cur.text.length - found.to.ch; } return len; } // Find the longest line in the document. function findMaxLine(cm) { var d = cm.display, doc = cm.doc; d.maxLine = getLine(doc, doc.first); d.maxLineLength = lineLength(d.maxLine); d.maxLineChanged = true; doc.iter(function (line) { var len = lineLength(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 = indexOf(options.gutters, "CodeMirror-linenumbers"); if (found == -1 && options.lineNumbers) { options.gutters = options .gutters .concat(["CodeMirror-linenumbers"]); } else if (found > -1 && !options.lineNumbers) { options.gutters = options .gutters .slice(0); options .gutters .splice(found, 1); } } // SCROLLBARS Prepare DOM reads needed to update the scrollbars. Done in one // shot to minimize update/measure roundtrips. function measureForScrollbars(cm) { var d = cm.display, gutterW = d.gutters.offsetWidth; var docH = Math.round(cm.doc.height + paddingVert(cm.display)); return { barLeft : cm.options.fixedGutter ? gutterW : 0, clientHeight : d.scroller.clientHeight, clientWidth : d.scroller.clientWidth, docHeight : docH, gutterWidth : gutterW, nativeBarWidth: d.nativeBarWidth, scrollHeight : docH + scrollGap(cm) + d.barHeight, scrollWidth : d.scroller.scrollWidth, viewHeight : d.wrapper.clientHeight, viewWidth : d.wrapper.clientWidth }; } function NativeScrollbars(place, scroll, cm) { this.cm = cm; var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar"); var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar"); place(vert); place(horiz); on(vert, "scroll", function () { if (vert.clientHeight) { scroll(vert.scrollTop, "vertical"); } }); on(horiz, "scroll", function () { if (horiz.clientWidth) { scroll(horiz.scrollLeft, "horizontal"); } }); this.checkedOverlay = false; // Need to set a minimum width to see the scrollbar on IE7 (but must not set it // on IE8). if (ie && ie_version < 8) { this.horiz.style.minHeight = this.vert.style.minWidth = "18px"; } } NativeScrollbars.prototype = copyObj({ clear : function () { var parent = this.horiz.parentNode; parent.removeChild(this.horiz); parent.removeChild(this.vert); }, overlayHack : function () { var w = mac && !mac_geMountainLion ? "12px" : "18px"; this.horiz.style.minHeight = this.vert.style.minWidth = w; var self = this; var barMouseDown = function (e) { if (e_target(e) != self.vert && e_target(e) != self.horiz) { operation(self.cm, onMouseDown)(e); }; } on(this.vert, "mousedown", barMouseDown); on(this.horiz, "mousedown", barMouseDown); }, setScrollLeft: function (pos) { if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos; } }, setScrollTop : function (pos) { if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos; } }, update : function (measure) { var needsH = measure.scrollWidth > measure.clientWidth + 1; var needsV = measure.scrollHeight > measure.clientHeight + 1; var sWidth = measure.nativeBarWidth; if (needsV) { this.vert.style.display = "block"; this.vert.style.bottom = needsH ? sWidth + "px" : "0"; var totalHeight = measure.viewHeight - (needsH ? sWidth : 0); // A bug in IE8 can cause this value to be negative, so guard it. this.vert.firstChild.style.height = Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px"; } else { this.vert.style.display = ""; this.vert.firstChild.style.height = "0"; } if (needsH) { this.horiz.style.display = "block"; this.horiz.style.right = needsV ? sWidth + "px" : "0"; this.horiz.style.left = measure.barLeft + "px"; var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0); this.horiz.firstChild.style.width = (measure.scrollWidth - measure.clientWidth + totalWidth) + "px"; } else { this.horiz.style.display = ""; this.horiz.firstChild.style.width = "0"; } if (!this.checkedOverlay && measure.clientHeight > 0) { if (sWidth == 0) { this.overlayHack(); } this.checkedOverlay = true; } return { bottom: needsH ? sWidth : 0, right : needsV ? sWidth : 0 }; } }, NativeScrollbars.prototype); function NullScrollbars() {} NullScrollbars.prototype = copyObj({ clear : function () {}, setScrollLeft: function () {}, setScrollTop : function () {}, update : function () { return {bottom: 0, right: 0}; } }, NullScrollbars.prototype); codeMirror.scrollbarModel = { "native": NativeScrollbars, "null" : NullScrollbars }; function initScrollbars(cm) { if (cm.display.scrollbars) { cm .display .scrollbars .clear(); if (cm.display.scrollbars.addClass) { rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); } } cm.display.scrollbars = new codeMirror.scrollbarModel[cm.options.scrollbarStyle](function (node) { cm .display .wrapper .insertBefore(node, cm.display.scrollbarFiller); on(node, "mousedown", function () { if (cm.state.focused) { setTimeout(bind(focusInput, cm), 0); } }); node.setAttribute("not-content", "true"); }, function (pos, axis) { if (axis == "horizontal") { setScrollLeft(cm, pos); } else { setScrollTop(cm, pos); } }, cm); if (cm.display.scrollbars.addClass) { addClass(cm.display.wrapper, cm.display.scrollbars.addClass); } } function updateScrollbars(cm, measure) { if (!measure) { measure = measureForScrollbars(cm); } var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight; updateScrollbarsInner(cm, measure); for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i += 1) { if (startWidth != cm.display.barWidth && cm.options.lineWrapping) { updateHeightsInViewport(cm); } updateScrollbarsInner(cm, measureForScrollbars(cm)); startWidth = cm.display.barWidth; startHeight = cm.display.barHeight; } } // Re-synchronize the fake scrollbars with the actual size of the content. function updateScrollbarsInner(cm, measure) { var d = cm.display; var sizes = d .scrollbars .update(measure); d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"; d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"; if (sizes.right && sizes.bottom) { d.scrollbarFiller.style.display = "block"; d.scrollbarFiller.style.height = sizes.bottom + "px"; d.scrollbarFiller.style.width = sizes.right + "px"; } else { d.scrollbarFiller.style.display = ""; } if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { d.gutterFiller.style.display = "block"; d.gutterFiller.style.height = sizes.bottom + "px"; d.gutterFiller.style.width = measure.gutterWidth + "px"; } else { d.gutterFiller.style.display = ""; } } // Compute the lines that are visible in a given viewport (defaults the the // current scroll position). viewport may contain top, height, and ensure (see // op.scrollToPos) properties. function visibleLines(display, doc, viewport) { var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop; top = Math.floor(top - paddingTop(display)); var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight; var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom); // Ensure is a {from: {line, ch}, to: {line, ch}} object, and forces those lines // into the viewport (if possible). if (viewport && viewport.ensure) { var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line; if (ensureFrom < from) { from = ensureFrom; to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight); } else if (Math.min(ensureTo, doc.lastLine()) >= to) { from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight); to = ensureTo; } } return { from: from, to : Math.max(to, from + 1) }; } // LINE NUMBERS Re-align line numbers and gutter marks to compensate for // horizontal scrolling. function alignHorizontally(cm) { var display = cm.display, view = display.view; 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, left = comp + "px"; for (var i = 0; i < view.length; i += 1) { if (!view[i].hidden) { if (cm.options.fixedGutter && view[i].gutter) { view[i].gutter.style.left = left; } var align = view[i].alignable; if (align) { for (var j = 0; j < align.length; j += 1) { align[j].style.left = left; } } } if (cm.options.fixedGutter) { display.gutters.style.left = (comp + gutterW) + "px"; } } } // Used to ensure that the line number gutter is still the right size for the // current document size. Returns true when an update is needed. 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"; updateGutterSpace(cm); return true; } return false; } function lineNumberFor(options, i) { return String(options.lineNumberFormatter(i + options.firstLineNumber)); } // Computes display.scroller.scrollLeft + display.gutters.offsetWidth, but using // getBoundingClientRect to get a sub-pixel-accurate result. function compensateForHScroll(display) { return display .scroller .getBoundingClientRect() .left - display .sizer .getBoundingClientRect() .left; } // DISPLAY DRAWING function DisplayUpdate(cm, viewport, force) { var display = cm.display; this.viewport = viewport; // Store some values that we'll need later (but don't want to force a relayout // for) this.visible = visibleLines(display, cm.doc, viewport); this.editorIsHidden = !display.wrapper.offsetWidth; this.wrapperHeight = display.wrapper.clientHeight; this.wrapperWidth = display.wrapper.clientWidth; this.oldDisplayWidth = displayWidth(cm); this.force = force; this.dims = getDimensions(cm); } function maybeClipScrollbars(cm) { var display = cm.display; if (!display.scrollbarsClipped && display.scroller.offsetWidth) { display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth; display.heightForcer.style.height = scrollGap(cm) + "px"; display.sizer.style.marginBottom = -display.nativeBarWidth + "px"; display.sizer.style.borderRightWidth = scrollGap(cm) + "px"; display.scrollbarsClipped = true; } } // Does the actual updating of the line display. Bails out (returning false) // when there is nothing to be done and forced is false. function updateDisplayIfNeeded(cm, update) { var display = cm.display, doc = cm.doc; if (update.editorIsHidden) { resetView(cm); return false; } // Bail out if the visible area is already rendered and nothing changed. if (!update.force && update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && display.renderedView == display.view && countDirtyView(cm) == 0) { return false; } if (maybeUpdateLineNumberWidth(cm)) { resetView(cm); update.dims = getDimensions(cm); } // Compute a suitable new viewport (from & to) var end = doc.first + doc.size; var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first); var to = Math.min(end, update.visible.to + cm.options.viewportMargin); if (display.viewFrom < from && from - display.viewFrom < 20) { from = Math.max(doc.first, display.viewFrom); } if (display.viewTo > to && display.viewTo - to < 20) { to = Math.min(end, display.viewTo); } if (sawCollapsedSpans) { from = visualLineNo(cm.doc, from); to = visualLineEndNo(cm.doc, to); } var different = from != display.viewFrom || to != display.viewTo || display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth; adjustView(cm, from, to); display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)); // Position the mover div to align with the current scroll position cm.display.mover.style.top = display.viewOffset + "px"; var toUpdate = countDirtyView(cm); if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) { return false; } // For big changes, we hide the enclosing element during the update, since that // speeds up the operations on most browsers. var focused = activeElt(); if (toUpdate > 4) { display.lineDiv.style.display = "none"; } patchDisplay(cm, display.updateLineNumbers, update.dims); if (toUpdate > 4) { display.lineDiv.style.display = ""; } display.renderedView = display.view; // There might have been a widget with a focused element that got hidden or // updated, if so re-focus it. if (focused && activeElt() != focused && focused.offsetHeight) { focused.focus(); } // Prevent selection and cursors from interfering with the scroll width and // height. removeChildren(display.cursorDiv); removeChildren(display.selectionDiv); display.gutters.style.height = 0; if (different) { display.lastWrapHeight = update.wrapperHeight; display.lastWrapWidth = update.wrapperWidth; startWorker(cm, 400); } display.updateLineNumbers = null; return true; } function postUpdateDisplay(cm, update) { var force = update.force, viewport = update.viewport; for (var first = true;; first = false) { if (first && cm.options.lineWrapping && update.oldDisplayWidth != displayWidth(cm)) { force = true; } else { force = false; // Clip forced viewport to actual scrollable area. if (viewport && viewport.top != null) { viewport = { top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top) }; } // Updated line heights might result in the drawn area not actually covering the // viewport. Keep looping until it does. update.visible = visibleLines(cm.display, cm.doc, viewport); if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) { break; } } if (!updateDisplayIfNeeded(cm, update)) { break; } updateHeightsInViewport(cm); var barMeasure = measureForScrollbars(cm); updateSelection(cm); setDocumentHeight(cm, barMeasure); updateScrollbars(cm, barMeasure); } signalLater(cm, "update", cm); if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { signalLater(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo; } } function updateDisplaySimple(cm, viewport) { var update = new DisplayUpdate(cm, viewport); if (updateDisplayIfNeeded(cm, update)) { updateHeightsInViewport(cm); postUpdateDisplay(cm, update); var barMeasure = measureForScrollbars(cm); updateSelection(cm); setDocumentHeight(cm, barMeasure); updateScrollbars(cm, barMeasure); } } function setDocumentHeight(cm, measure) { cm.display.sizer.style.minHeight = measure.docHeight + "px"; var total = measure.docHeight + cm.display.barHeight; cm.display.heightForcer.style.top = total + "px"; cm.display.gutters.style.height = Math.max(total + scrollGap(cm), measure.clientHeight) + "px"; } // Read the actual heights of the rendered lines, and update their stored // heights to match. function updateHeightsInViewport(cm) { var display = cm.display; var prevBottom = display.lineDiv.offsetTop; for (var i = 0; i < display.view.length; i += 1) { var cur = display.view[i], height; if (cur.hidden) { continue; } if (ie && ie_version < 8) { var bot = cur.node.offsetTop + cur.node.offsetHeight; height = bot - prevBottom; prevBottom = bot; } else { var box = cur .node .getBoundingClientRect(); height = box.bottom - box.top; } var diff = cur.line.height - height; if (height < 2) { height = textHeight(display); } if (diff > .001 || diff < -.001) { updateLineHeight(cur.line, height); updateWidgetHeight(cur.line); if (cur.rest) { for (var j = 0; j < cur.rest.length; j += 1) { updateWidgetHeight(cur.rest[j]); } } } } } // Read and store the height of line widgets associated with the given line. function updateWidgetHeight(line) { if (line.widgets) { for (var i = 0; i < line.widgets.length; i += 1) { line .widgets[i] .height = line .widgets[i] .node .offsetHeight; } } } // Do a bulk-read of the DOM positions and sizes needed to draw the view, so // that we don't interleave reading and writing to the DOM. function getDimensions(cm) { var d = cm.display, left = {}, width = {}; var gutterLeft = d.gutters.clientLeft; for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, i += 1) { left[ cm .options .gutters[i] ] = n.offsetLeft + n.clientLeft + gutterLeft; width[ cm .options .gutters[i] ] = n.clientWidth; } return {fixedPos: compensateForHScroll(d), gutterLeft: left, gutterTotalWidth: d.gutters.offsetWidth, gutterWidth: width, wrapperWidth: d.wrapper.clientWidth}; } // Sync the actual display DOM structure with display.view, removing nodes for // lines that are no longer in view, and creating the ones that are not there // yet, and updating the ones that are out of date. function patchDisplay(cm, updateNumbersFrom, dims) { var display = cm.display, lineNumbers = cm.options.lineNumbers; var container = display.lineDiv, cur = container.firstChild; function rm(node) { var next = node.nextSibling; // Works around a throw-scroll bug in OS X Webkit if (webkit && mac && cm.display.currentWheelTarget == node) { node.style.display = "none"; } else { node .parentNode .removeChild(node); } return next; } var view = display.view, lineN = display.viewFrom; // Loop over the elements in the view, syncing cur (the DOM nodes in // display.lineDiv) with the view as we go. for (var i = 0; i < view.length; i += 1) { var lineView = view[i]; if (lineView.hidden) {} else if (!lineView.node) { // Not drawn yet var node = buildLineElement(cm, lineView, lineN, dims); container.insertBefore(node, cur); } else { // Already drawn while (cur != lineView.node) { cur = rm(cur); } var updateNumber = lineNumbers && updateNumbersFrom != null && updateNumbersFrom <= lineN && lineView.lineNumber; if (lineView.changes) { if (indexOf(lineView.changes, "gutter") > -1) { updateNumber = false; } updateLineForChanges(cm, lineView, lineN, dims); } if (updateNumber) { removeChildren(lineView.lineNumber); lineView .lineNumber .appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))); } cur = lineView.node.nextSibling; } lineN += lineView.size; } while (cur) { cur = rm(cur); } } // When an aspect of a line changes, a string is added to lineView.changes. This // updates the relevant part of the line's DOM structure. function updateLineForChanges(cm, lineView, lineN, dims) { for (var j = 0; j < lineView.changes.length; j += 1) { var type = lineView.changes[j]; if (type == "text") { updateLineText(cm, lineView); } else if (type == "gutter") { updateLineGutter(cm, lineView, lineN, dims); } else if (type == "class") { updateLineClasses(lineView); } else if (type == "widget") { updateLineWidgets(lineView, dims); } } lineView.changes = null; } // Lines with gutter elements, widgets or a background class need to be wrapped, // and have the extra elements added to the wrapper div function ensureLineWrapped(lineView) { if (lineView.node == lineView.text) { lineView.node = elt("div", null, null, "position: relative"); if (lineView.text.parentNode) { lineView .text .parentNode .replaceChild(lineView.node, lineView.text); } lineView .node .appendChild(lineView.text); if (ie && ie_version < 8) { lineView.node.style.zIndex = 2; } } return lineView.node; } function updateLineBackground(lineView) { var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass; if (cls) { cls += " CodeMirror-linebackground"; } if (lineView.background) { if (cls) { lineView.background.className = cls; } else { lineView .background .parentNode .removeChild(lineView.background); lineView.background = null; } } else if (cls) { var wrap = ensureLineWrapped(lineView); lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild); } } // Wrapper around buildLineContent which will reuse the structure in // display.externalMeasured when possible. function getLineContent(cm, lineView) { var ext = cm.display.externalMeasured; if (ext && ext.line == lineView.line) { cm.display.externalMeasured = null; lineView.measure = ext.measure; return ext.built; } return buildLineContent(cm, lineView); } // Redraw the line's text. Interacts with the background and text classes // because the mode may output tokens that influence these classes. function updateLineText(cm, lineView) { var cls = lineView.text.className; var built = getLineContent(cm, lineView); if (lineView.text == lineView.node) { lineView.node = built.pre; } lineView .text .parentNode .replaceChild(built.pre, lineView.text); lineView.text = built.pre; if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { lineView.bgClass = built.bgClass; lineView.textClass = built.textClass; updateLineClasses(lineView); } else if (cls) { lineView.text.className = cls; } } function updateLineClasses(lineView) { updateLineBackground(lineView); if (lineView.line.wrapClass) { ensureLineWrapped(lineView).className = lineView.line.wrapClass; } else if (lineView.node != lineView.text) { lineView.node.className = ""; } var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass; lineView.text.className = textClass || ""; } function updateLineGutter(cm, lineView, lineN, dims) { if (lineView.gutter) { lineView .node .removeChild(lineView.gutter); lineView.gutter = null; } var markers = lineView.line.gutterMarkers; if (cm.options.lineNumbers || markers) { var wrap = ensureLineWrapped(lineView); var gutterWrap = lineView.gutter = wrap.insertBefore(elt("div", null, "CodeMirror-gutter-wrapper", "left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px; width: " + dims.gutterTotalWidth + "px"), lineView.text); if (lineView.line.gutterClass) { gutterWrap.className += " " + lineView.line.gutterClass; } if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) { lineView.lineNumber = gutterWrap.appendChild(elt("div", lineNumberFor(cm.options, lineN), "CodeMirror-linenumber CodeMirror-gutter-elt", "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: " + cm.display.lineNumInnerWidth + "px")); } if (markers) { for (var k = 0; k < cm.options.gutters.length; k += 1) { 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")); } } } } } function updateLineWidgets(lineView, dims) { if (lineView.alignable) { lineView.alignable = null; } for (var