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
Plain Text
// 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