diff --git a/doc/index.html b/doc/index.html index 540b90b..76d9152 100644 --- a/doc/index.html +++ b/doc/index.html @@ -12,11 +12,13 @@
@@ -25,19 +27,17 @@- The core of mergely is a javascript-based diff and customizable markup engine. - Mergely provides a rich API that enables integration into your own application. It - can be used as a diff tool (read-only) or as both a diff and merge + The core of mergely is a javascript-based Longest Common Subsequence diff algorithm (LCS) + and customizable markup engine. Mergely provides a rich API that enables integration into + your own application. It can be used as a diff tool (read-only) or as both a diff and merge tool for plain text, CSS, HTML, XML, javascript, PHP, C, C++, etc.
-- -
- +Mergely requires jQuery and CodeMirror. @@ -87,6 +87,8 @@ $(document).ready(function () {
' + // Absolutely positioned blinky cursor - '' + // DIVs containing the selection and the actual code - '
' + line.getHTML(tabText) + ''; + var lineElement = lineContent(line); + if (line.className) lineElement.className = line.className; // Kludge to make sure the styled element lies behind the selection (by z-index) - if (line.className) - html = '
' + html + "
' : ""), text); - for (var j = 1; j < line.height; ++j) html.push(""); + var markerElement = fragment.appendChild(elt("pre", null, marker && marker.style)); + markerElement.innerHTML = text; + for (var j = 1; j < line.height; ++j) { + markerElement.appendChild(elt("br")); + markerElement.appendChild(document.createTextNode("\u00a0")); + } + if (!marker) normalNode = i; } ++i; }); gutter.style.display = "none"; - gutterText.innerHTML = html.join(""); - var minwidth = String(doc.size).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = ""; - while (val.length + pad.length < minwidth) pad += "\u00a0"; - if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild); + removeChildrenAndAdd(gutterText, fragment); + // Make sure scrolling doesn't cause number gutter size to pop + if (normalNode != null && options.lineNumbers) { + var node = gutterText.childNodes[normalNode - showingFrom]; + var minwidth = String(doc.size).length, val = eltText(node.firstChild), pad = ""; + while (val.length + pad.length < minwidth) pad += "\u00a0"; + if (pad) node.insertBefore(document.createTextNode(pad), node.firstChild); + } gutter.style.display = ""; + var resized = Math.abs((parseInt(lineSpace.style.marginLeft) || 0) - gutter.offsetWidth) > 2; lineSpace.style.marginLeft = gutter.offsetWidth + "px"; gutterDirty = false; + return resized; } function updateSelection() { var collapsed = posEq(sel.from, sel.to); @@ -1030,22 +1250,26 @@ var CodeMirror = (function() { cursor.style.display = ""; selectionDiv.style.display = "none"; } else { - var sameLine = fromPos.y == toPos.y, html = ""; - function add(left, top, right, height) { - html += ''; - } + var sameLine = fromPos.y == toPos.y, fragment = document.createDocumentFragment(); + var clientWidth = lineSpace.clientWidth || lineSpace.offsetWidth; + var clientHeight = lineSpace.clientHeight || lineSpace.offsetHeight; + var add = function(left, top, right, height) { + var rstyle = quirksMode ? "width: " + (!right ? clientWidth : clientWidth - right - left) + "px" + : "right: " + right + "px"; + fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + + "px; top: " + top + "px; " + rstyle + "; height: " + height + "px")); + }; if (sel.from.ch && fromPos.y >= 0) { - var right = sameLine ? lineSpace.clientWidth - toPos.x : 0; + var right = sameLine ? clientWidth - toPos.x : 0; add(fromPos.x, fromPos.y, right, th); } var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0)); - var middleHeight = Math.min(toPos.y, lineSpace.clientHeight) - middleStart; + var middleHeight = Math.min(toPos.y, clientHeight) - middleStart; if (middleHeight > 0.2 * th) add(0, middleStart, 0, middleHeight); - if ((!sameLine || !sel.from.ch) && toPos.y < lineSpace.clientHeight - .5 * th) - add(0, toPos.y, lineSpace.clientWidth - toPos.x, th); - selectionDiv.innerHTML = html; + if ((!sameLine || !sel.from.ch) && toPos.y < clientHeight - .5 * th) + add(0, toPos.y, clientWidth - toPos.x, th); + removeChildrenAndAdd(selectionDiv, fragment); cursor.style.display = "none"; selectionDiv.style.display = ""; } @@ -1074,13 +1298,32 @@ var CodeMirror = (function() { if (posLess(to, from)) {var tmp = to; to = from; from = tmp;} // Skip over hidden lines. - if (from.line != oldFrom) from = skipHidden(from, oldFrom, sel.from.ch); + if (from.line != oldFrom) { + var from1 = skipHidden(from, oldFrom, sel.from.ch); + // If there is no non-hidden line left, force visibility on current line + if (!from1) setLineHidden(from.line, false); + else from = from1; + } if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch); if (posEq(from, to)) sel.inverted = false; else if (posEq(from, sel.to)) sel.inverted = false; else if (posEq(to, sel.from)) sel.inverted = true; + if (options.autoClearEmptyLines && posEq(sel.from, sel.to)) { + var head = sel.inverted ? from : to; + if (head.line != sel.from.line && sel.from.line < doc.size) { + var oldLine = getLine(sel.from.line); + if (/^\s+$/.test(oldLine.text)) + setTimeout(operation(function() { + if (oldLine.parent && /^\s+$/.test(oldLine.text)) { + var no = lineNo(oldLine); + replaceRange("", {line: no, ch: 0}, {line: no, ch: oldLine.text.length}); + } + }, 10)); + } + } + sel.from = from; sel.to = to; selectionChanged = true; } @@ -1091,13 +1334,14 @@ var CodeMirror = (function() { var line = getLine(lNo); if (!line.hidden) { var ch = pos.ch; - if (ch > oldCh || ch > line.text.length) ch = line.text.length; + if (toEnd || ch > oldCh || ch > line.text.length) ch = line.text.length; return {line: lNo, ch: ch}; } lNo += dir; } } var line = getLine(pos.line); + var toEnd = pos.ch == line.text.length && pos.ch != oldCh; if (!line.hidden) return pos; if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1); else return getNonHidden(-1) || getNonHidden(1); @@ -1157,26 +1401,37 @@ var CodeMirror = (function() { else replaceRange("", sel.from, findPosH(dir, unit)); userSelChange = true; } - var goalColumn = null; function moveV(dir, unit) { var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true); if (goalColumn != null) pos.x = goalColumn; - if (unit == "page") dist = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight); - else if (unit == "line") dist = textHeight(); - var target = coordsChar(pos.x, pos.y + dist * dir + 2); + if (unit == "page") { + var screen = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight); + var target = coordsChar(pos.x, pos.y + screen * dir); + } else if (unit == "line") { + var th = textHeight(); + var target = coordsChar(pos.x, pos.y + .5 * th + dir * th); + } + if (unit == "page") scrollbar.scrollTop += localCoords(target, true).y - pos.y; setCursor(target.line, target.ch, true); goalColumn = pos.x; } - function selectWordAt(pos) { + function findWordAt(pos) { var line = getLine(pos.line).text; var start = pos.ch, end = pos.ch; - while (start > 0 && isWordChar(line.charAt(start - 1))) --start; - while (end < line.length && isWordChar(line.charAt(end))) ++end; - setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end}); + if (line) { + if (pos.after === false || end == line.length) --start; else ++end; + var startChar = line.charAt(start); + var check = isWordChar(startChar) ? isWordChar : + /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} : + function(ch) {return !/\s/.test(ch) && isWordChar(ch);}; + while (start > 0 && check(line.charAt(start - 1))) --start; + while (end < line.length && check(line.charAt(end))) ++end; + } + return {from: {line: pos.line, ch: start}, to: {line: pos.line, ch: end}}; } function selectLine(line) { - setSelectionUser({line: line, ch: 0}, {line: line, ch: getLine(line).text.length}); + setSelectionUser({line: line, ch: 0}, clipPos({line: line + 1, ch: 0})); } function indentSelected(mode) { if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode); @@ -1193,35 +1448,34 @@ var CodeMirror = (function() { var line = getLine(n), curSpace = line.indentation(options.tabSize), curSpaceString = line.text.match(/^\s*/)[0], indentation; + if (how == "smart") { + indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text); + if (indentation == Pass) how = "prev"; + } if (how == "prev") { if (n) indentation = getLine(n-1).indentation(options.tabSize); else indentation = 0; } - else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text); else if (how == "add") indentation = curSpace + options.indentUnit; else if (how == "subtract") indentation = curSpace - options.indentUnit; indentation = Math.max(0, indentation); var diff = indentation - curSpace; - if (!diff) { - if (sel.from.line != n && sel.to.line != n) return; - var indentString = curSpaceString; - } - else { - var indentString = "", pos = 0; - if (options.indentWithTabs) - for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";} - while (pos < indentation) {++pos; indentString += " ";} - } + var indentString = "", pos = 0; + if (options.indentWithTabs) + for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";} + if (pos < indentation) indentString += spaceStr(indentation - pos); - replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length}); + if (indentString != curSpaceString) + replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length}); + line.stateAfter = null; } function loadMode() { mode = CodeMirror.getMode(options, options.mode); doc.iter(0, doc.size, function(line) { line.stateAfter = null; }); - work = [0]; - startWorker(); + frontier = 0; + startWorker(100); } function gutterChanged() { var visible = options.gutter || options.lineNumbers; @@ -1238,86 +1492,93 @@ var CodeMirror = (function() { var guess = Math.ceil(line.text.length / perLine) || 1; if (guess != 1) updateLineHeight(line, guess); }); - lineSpace.style.width = code.style.width = ""; + lineSpace.style.minWidth = widthForcer.style.left = ""; } else { wrapper.className = wrapper.className.replace(" CodeMirror-wrap", ""); - maxWidth = null; maxLine = ""; + computeMaxLength(); doc.iter(0, doc.size, function(line) { if (line.height != 1 && !line.hidden) updateLineHeight(line, 1); - if (line.text.length > maxLine.length) maxLine = line.text; }); } changes.push({from: 0, to: doc.size}); } - function computeTabText() { - for (var str = '', i = 0; i < options.tabSize; ++i) str += " "; - return str + ""; - } - function tabsChanged() { - tabText = computeTabText(); - updateDisplay(true); - } function themeChanged() { - scroller.className = scroller.className.replace(/\s*cm-s-\w+/g, "") + + scroller.className = scroller.className.replace(/\s*cm-s-\S+/g, "") + options.theme.replace(/(^|\s)\s*/g, " cm-s-"); } + function keyMapChanged() { + var style = keyMap[options.keyMap].style; + wrapper.className = wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + + (style ? " cm-keymap-" + style : ""); + } - function TextMarker() { this.set = []; } + function TextMarker(type, style) { this.lines = []; this.type = type; if (style) this.style = style; } TextMarker.prototype.clear = operation(function() { - var min = Infinity, max = -Infinity; - for (var i = 0, e = this.set.length; i < e; ++i) { - var line = this.set[i], mk = line.marked; - if (!mk || !line.parent) continue; - var lineN = lineNo(line); - min = Math.min(min, lineN); max = Math.max(max, lineN); - for (var j = 0; j < mk.length; ++j) - if (mk[j].set == this.set) mk.splice(j--, 1); + var min, max; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (span.from != null) min = lineNo(line); + if (span.to != null) max = lineNo(line); + line.markedSpans = removeMarkedSpan(line.markedSpans, span); } - if (min != Infinity) - changes.push({from: min, to: max + 1}); + if (min != null) changes.push({from: min, to: max + 1}); + this.lines.length = 0; + this.explicitlyCleared = true; }); TextMarker.prototype.find = function() { var from, to; - for (var i = 0, e = this.set.length; i < e; ++i) { - var line = this.set[i], mk = line.marked; - for (var j = 0; j < mk.length; ++j) { - var mark = mk[j]; - if (mark.set == this.set) { - if (mark.from != null || mark.to != null) { - var found = lineNo(line); - if (found != null) { - if (mark.from != null) from = {line: found, ch: mark.from}; - if (mark.to != null) to = {line: found, ch: mark.to}; - } - } - } + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (span.from != null || span.to != null) { + var found = lineNo(line); + if (span.from != null) from = {line: found, ch: span.from}; + if (span.to != null) to = {line: found, ch: span.to}; } } - return {from: from, to: to}; + if (this.type == "bookmark") return from; + return from && {from: from, to: to}; }; - function markText(from, to, className) { + function markText(from, to, className, options) { from = clipPos(from); to = clipPos(to); - var tm = new TextMarker(); - function add(line, from, to, className) { - getLine(line).addMark(new MarkedText(from, to, className, tm.set)); - } - if (from.line == to.line) add(from.line, from.ch, to.ch, className); - else { - add(from.line, from.ch, null, className); - for (var i = from.line + 1, e = to.line; i < e; ++i) - add(i, null, null, className); - add(to.line, null, to.ch, className); - } + var marker = new TextMarker("range", className); + if (options) for (var opt in options) if (options.hasOwnProperty(opt)) + marker[opt] = options[opt]; + var curLine = from.line; + doc.iter(curLine, to.line + 1, function(line) { + var span = {from: curLine == from.line ? from.ch : null, + to: curLine == to.line ? to.ch : null, + marker: marker}; + line.markedSpans = (line.markedSpans || []).concat([span]); + marker.lines.push(line); + ++curLine; + }); changes.push({from: from.line, to: to.line + 1}); - return tm; + return marker; } function setBookmark(pos) { pos = clipPos(pos); - var bm = new Bookmark(pos.ch); - getLine(pos.line).addMark(bm); - return bm; + var marker = new TextMarker("bookmark"), line = getLine(pos.line); + history.addChange(pos.line, 1, [newHL(line.text, line.markedSpans)], true); + var span = {from: pos.ch, to: pos.ch, marker: marker}; + line.markedSpans = (line.markedSpans || []).concat([span]); + marker.lines.push(line); + return marker; + } + + function findMarksAt(pos) { + pos = clipPos(pos); + var markers = [], spans = getLine(pos.line).markedSpans; + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if ((span.from == null || span.from <= pos.ch) && + (span.to == null || span.to >= pos.ch)) + markers.push(span.marker); + } + return markers; } function addGutterMarker(line, text, className) { @@ -1341,10 +1602,11 @@ var CodeMirror = (function() { else return null; return line; } - function setLineClass(handle, className) { + function setLineClass(handle, className, bgClassName) { return changeLine(handle, function(line) { - if (line.className != className) { + if (line.className != className || line.bgClassName != bgClassName) { line.className = className; + line.bgClassName = bgClassName; return true; } }); @@ -1353,11 +1615,20 @@ var CodeMirror = (function() { return changeLine(handle, function(line, no) { if (line.hidden != hidden) { line.hidden = hidden; + if (!options.lineWrapping) { + if (hidden && line.text.length == maxLine.text.length) { + updateMaxLine = true; + } else if (!hidden && line.text.length > maxLine.text.length) { + maxLine = line; updateMaxLine = false; + } + } updateLineHeight(line, hidden ? 0 : 1); var fline = sel.from.line, tline = sel.to.line; if (hidden && (fline == no || tline == no)) { var from = fline == no ? skipHidden({line: fline, ch: 0}, fline, 0) : sel.from; var to = tline == no ? skipHidden({line: tline, ch: 0}, tline, 0) : sel.to; + // Can't hide the last visible line, we'd have no place to put the cursor + if (!to) return; setSelection(from, to); } return (gutterDirty = true); @@ -1371,70 +1642,25 @@ var CodeMirror = (function() { var n = line; line = getLine(line); if (!line) return null; - } - else { + } else { var n = lineNo(line); if (n == null) return null; } var marker = line.gutterMarker; return {line: n, handle: line, text: line.text, markerText: marker && marker.text, - markerClass: marker && marker.style, lineClass: line.className}; + markerClass: marker && marker.style, lineClass: line.className, bgClass: line.bgClassName}; } - function stringWidth(str) { - measure.innerHTML = "
"); - html.push("x"; - measure.firstChild.firstChild.firstChild.nodeValue = str; - return measure.firstChild.firstChild.offsetWidth || 10; - } - // These are used to go from pixel positions to character - // positions, taking varying character widths into account. - function charFromX(line, x) { - if (x <= 0) return 0; - var lineObj = getLine(line), text = lineObj.text; - function getX(len) { - measure.innerHTML = "" + lineObj.getHTML(tabText, len) + ""; - return measure.firstChild.firstChild.offsetWidth; - } - var from = 0, fromX = 0, to = text.length, toX; - // Guess a suitable upper bound for our search. - var estimated = Math.min(to, Math.ceil(x / charWidth())); - for (;;) { - var estX = getX(estimated); - if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2)); - else {toX = estX; to = estimated; break;} - } - if (x > toX) return to; - // Try to guess a suitable lower bound as well. - estimated = Math.floor(to * 0.8); estX = getX(estimated); - if (estX < x) {from = estimated; fromX = estX;} - // Do a binary search between these bounds. - for (;;) { - if (to - from <= 1) return (toX - x > x - fromX) ? from : to; - var middle = Math.ceil((from + to) / 2), middleX = getX(middle); - if (middleX > x) {to = middle; toX = middleX;} - else {from = middle; fromX = middleX;} - } - } - - var tempId = Math.floor(Math.random() * 0xffffff).toString(16); function measureLine(line, ch) { if (ch == 0) return {top: 0, left: 0}; - var extra = ""; - // Include extra text at the end to make sure the measured line is wrapped in the right way. - if (options.lineWrapping) { - var end = line.text.indexOf(" ", ch + 2); - extra = htmlEscape(line.text.slice(ch + 1, end < 0 ? line.text.length : end + (ie ? 5 : 0))); - } - measure.innerHTML = "" + line.getHTML(tabText, ch) + - '' + htmlEscape(line.text.charAt(ch) || " ") + "" + - extra + ""; - var elt = document.getElementById("CodeMirror-temp-" + tempId); - var top = elt.offsetTop, left = elt.offsetLeft; + var pre = lineContent(line, ch); + removeChildrenAndAdd(measure, pre); + var anchor = pre.anchor; + var top = anchor.offsetTop, left = anchor.offsetLeft; // Older IEs report zero offsets for spans directly after a wrap if (ie && top == 0 && left == 0) { - var backup = document.createElement("span"); - backup.innerHTML = "x"; - elt.parentNode.insertBefore(backup, elt.nextSibling); + var backup = elt("span", "x"); + anchor.parentNode.insertBefore(backup, anchor.nextSibling); top = backup.offsetTop; } return {top: top, left: left}; @@ -1451,17 +1677,19 @@ var CodeMirror = (function() { } // Coords must be lineSpace-local function coordsChar(x, y) { - if (y < 0) y = 0; var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th); + if (heightPos < 0) return {line: 0, ch: 0}; var lineNo = lineAtHeight(doc, heightPos); if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length}; var lineObj = getLine(lineNo), text = lineObj.text; var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0; if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0}; + var wrongLine = false; function getX(len) { var sp = measureLine(lineObj, len); if (tw) { var off = Math.round(sp.top / th); + wrongLine = off != innerOff; return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth); } return sp.left; @@ -1480,9 +1708,12 @@ var CodeMirror = (function() { if (estX < x) {from = estimated; fromX = estX;} // Do a binary search between these bounds. for (;;) { - if (to - from <= 1) return {line: lineNo, ch: (toX - x > x - fromX) ? from : to}; + if (to - from <= 1) { + var after = x - fromX < toX - x; + return {line: lineNo, ch: after ? from : to, after: after}; + } var middle = Math.ceil((from + to) / 2), middleX = getX(middle); - if (middleX > x) {to = middle; toX = middleX;} + if (middleX > x) {to = middle; toX = middleX; if (wrongLine) toX += 1000; } else {from = middle; fromX = middleX;} } } @@ -1491,26 +1722,32 @@ var CodeMirror = (function() { return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot}; } - var cachedHeight, cachedHeightFor, measureText; + var cachedHeight, cachedHeightFor, measurePre; function textHeight() { - if (measureText == null) { - measureText = ""; - for (var i = 0; i < 49; ++i) measureText += "x"; + if (measurePre == null) { + measurePre = elt("pre"); + for (var i = 0; i < 49; ++i) { + measurePre.appendChild(document.createTextNode("x")); + measurePre.appendChild(elt("br")); + } + measurePre.appendChild(document.createTextNode("x")); } var offsetHeight = lineDiv.clientHeight; if (offsetHeight == cachedHeightFor) return cachedHeight; cachedHeightFor = offsetHeight; - measure.innerHTML = measureText; + removeChildrenAndAdd(measure, measurePre.cloneNode(true)); cachedHeight = measure.firstChild.offsetHeight / 50 || 1; - measure.innerHTML = ""; + removeChildren(measure); return cachedHeight; } var cachedWidth, cachedWidthFor = 0; function charWidth() { if (scroller.clientWidth == cachedWidthFor) return cachedWidth; cachedWidthFor = scroller.clientWidth; - return (cachedWidth = stringWidth("x")); + var anchor = elt("span", "x"); + var pre = elt("pre", [anchor]); + removeChildrenAndAdd(measure, pre); + return (cachedWidth = anchor.offsetWidth || 10); } function paddingTop() {return lineSpace.offsetTop;} function paddingLeft() {return lineSpace.offsetLeft;} @@ -1527,9 +1764,10 @@ var CodeMirror = (function() { var offL = eltOffset(lineSpace, true); return coordsChar(x - offL.left, y - offL.top); } + var detectingSelectAll; function onContextMenu(e) { - var pos = posFromMouse(e); - if (!pos || window.opera) return; // Opera is difficult. + var pos = posFromMouse(e), scrollPos = scrollbar.scrollTop; + if (!pos || opera) return; // Opera is difficult. if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) operation(setCursor)(pos.line, pos.ch); @@ -1538,18 +1776,30 @@ var CodeMirror = (function() { input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " + "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; - leaveInputAlone = true; - var val = input.value = getSelection(); focusInput(); - input.select(); + resetInput(true); + // Adds "Select all" to context menu in FF + if (posEq(sel.from, sel.to)) input.value = prevInput = " "; + function rehide() { - var newVal = splitLines(input.value).join("\n"); - if (newVal != val) operation(replaceSelection)(newVal, "end"); inputDiv.style.position = "relative"; input.style.cssText = oldCSS; - leaveInputAlone = false; - resetInput(true); + if (ie_lt9) scrollbar.scrollTop = scrollPos; slowPoll(); + + // Try to detect the user choosing select-all + if (input.selectionStart != null) { + clearTimeout(detectingSelectAll); + var extval = input.value = " " + (posEq(sel.from, sel.to) ? "" : input.value), i = 0; + prevInput = " "; + input.selectionStart = 1; input.selectionEnd = extval.length; + detectingSelectAll = setTimeout(function poll(){ + if (prevInput == " " && input.selectionStart == 0) + operation(commands.selectAll)(instance); + else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500); + else resetInput(); + }, 200); + } } if (gecko) { @@ -1558,8 +1808,7 @@ var CodeMirror = (function() { mouseup(); setTimeout(rehide, 20); }, true); - } - else { + } else { setTimeout(rehide, 50); } } @@ -1571,7 +1820,7 @@ var CodeMirror = (function() { cursor.style.visibility = ""; blinker = setInterval(function() { cursor.style.visibility = (on = !on) ? "" : "hidden"; - }, 650); + }, options.cursorBlinkRate); } var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"}; @@ -1589,7 +1838,7 @@ var CodeMirror = (function() { var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur; for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) { var text = st[i]; - if (st[i+1] != null && st[i+1] != style) {pos += d * text.length; continue;} + if (st[i+1] != style) {pos += d * text.length; continue;} for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) { if (pos >= from && pos < to && re.test(cur = text.charAt(j))) { var match = matching[cur]; @@ -1634,66 +1883,39 @@ var CodeMirror = (function() { return minline; } function getStateBefore(n) { - var start = findStartLine(n), state = start && getLine(start-1).stateAfter; + var pos = findStartLine(n), state = pos && getLine(pos-1).stateAfter; if (!state) state = startState(mode); else state = copyState(mode, state); - doc.iter(start, n, function(line) { - line.highlight(mode, state, options.tabSize); - line.stateAfter = copyState(mode, state); + doc.iter(pos, n, function(line) { + line.process(mode, state, options.tabSize); + line.stateAfter = (pos == n - 1 || pos % 5 == 0) ? copyState(mode, state) : null; }); - if (start < n) changes.push({from: start, to: n}); - if (n < doc.size && !getLine(n).stateAfter) work.push(n); return state; } - function highlightLines(start, end) { - var state = getStateBefore(start); - doc.iter(start, end, function(line) { - line.highlight(mode, state, options.tabSize); - line.stateAfter = copyState(mode, state); - }); - } function highlightWorker() { - var end = +new Date + options.workTime; - var foundWork = work.length; - while (work.length) { - if (!getLine(showingFrom).stateAfter) var task = showingFrom; - else var task = work.pop(); - if (task >= doc.size) continue; - var start = findStartLine(task), state = start && getLine(start-1).stateAfter; - if (state) state = copyState(mode, state); - else state = startState(mode); - - var unchanged = 0, compare = mode.compareStates, realChange = false, - i = start, bail = false; - doc.iter(i, doc.size, function(line) { - var hadState = line.stateAfter; - if (+new Date > end) { - work.push(i); - startWorker(options.workDelay); - if (realChange) changes.push({from: task, to: i + 1}); - return (bail = true); - } - var changed = line.highlight(mode, state, options.tabSize); - if (changed) realChange = true; + if (frontier >= showingTo) return; + var end = +new Date + options.workTime, state = copyState(mode, getStateBefore(frontier)); + var startFrontier = frontier; + doc.iter(frontier, showingTo, function(line) { + if (frontier >= showingFrom) { // Visible + line.highlight(mode, state, options.tabSize); line.stateAfter = copyState(mode, state); - if (compare) { - if (hadState && compare(hadState, state)) return true; - } else { - if (changed !== false || !hadState) unchanged = 0; - else if (++unchanged > 3 && (!mode.indent || mode.indent(hadState, "") == mode.indent(state, ""))) - return true; - } - ++i; - }); - if (bail) return; - if (realChange) changes.push({from: task, to: i + 1}); - } - if (foundWork && options.onHighlightComplete) - options.onHighlightComplete(instance); + } else { + line.process(mode, state, options.tabSize); + line.stateAfter = frontier % 5 == 0 ? copyState(mode, state) : null; + } + ++frontier; + if (+new Date > end) { + startWorker(options.workDelay); + return true; + } + }); + if (showingTo > startFrontier && frontier >= showingFrom) + operation(function() {changes.push({from: startFrontier, to: frontier});})(); } function startWorker(time) { - if (!work.length) return; - highlight.set(time, operation(highlightWorker)); + if (frontier < showingTo) + highlight.set(time, highlightWorker); } // Operations are used to wrap changes in such a way that each @@ -1705,18 +1927,30 @@ var CodeMirror = (function() { changes = []; selectionChanged = false; callbacks = []; } function endOperation() { - var reScroll = false, updated; - if (selectionChanged) reScroll = !scrollCursorIntoView(); - if (changes.length) updated = updateDisplay(changes, true); - else { + if (updateMaxLine) computeMaxLength(); + if (maxLineChanged && !options.lineWrapping) { + var cursorWidth = widthForcer.offsetWidth, left = measureLine(maxLine, maxLine.text.length).left; + if (!ie_lt8) { + widthForcer.style.left = left + "px"; + lineSpace.style.minWidth = (left + cursorWidth) + "px"; + } + maxLineChanged = false; + } + var newScrollPos, updated; + if (selectionChanged) { + var coords = calculateCursorCoords(); + newScrollPos = calculateScrollPos(coords.x, coords.y, coords.x, coords.yBot); + } + if (changes.length || newScrollPos && newScrollPos.scrollTop != null) + updated = updateDisplay(changes, true, newScrollPos && newScrollPos.scrollTop); + if (!updated) { if (selectionChanged) updateSelection(); if (gutterDirty) updateGutter(); } - if (reScroll) scrollCursorIntoView(); - if (selectionChanged) {scrollEditorIntoView(); restartBlink();} + if (newScrollPos) scrollCursorIntoView(); + if (selectionChanged) restartBlink(); - if (focused && !leaveInputAlone && - (updateInput === true || (updateInput !== false && selectionChanged))) + if (focused && (updateInput === true || (updateInput !== false && selectionChanged))) resetInput(userSelChange); if (selectionChanged && options.matchBrackets) @@ -1724,11 +1958,11 @@ var CodeMirror = (function() { if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;} if (posEq(sel.from, sel.to)) matchBrackets(false); }), 20); - var tc = textChanged, cbs = callbacks; // these can be reset by callbacks - if (selectionChanged && options.onCursorActivity) + var sc = selectionChanged, cbs = callbacks; // these can be reset by callbacks + if (textChanged && options.onChange && instance) + options.onChange(instance, textChanged); + if (sc && options.onCursorActivity) options.onCursorActivity(instance); - if (tc && options.onChange && instance) - options.onChange(instance, tc); for (var i = 0; i < cbs.length; ++i) cbs[i](instance); if (updated && options.onUpdate) options.onUpdate(instance); } @@ -1742,10 +1976,16 @@ var CodeMirror = (function() { }; } + function compoundChange(f) { + history.startCompound(); + try { return f(); } finally { history.endCompound(); } + } + for (var ext in extensions) if (extensions.propertyIsEnumerable(ext) && !instance.propertyIsEnumerable(ext)) instance[ext] = extensions[ext]; + for (var i = 0; i < initHooks.length; ++i) initHooks[i](instance); return instance; } // (end of function CodeMirror) @@ -1761,26 +2001,31 @@ var CodeMirror = (function() { keyMap: "default", extraKeys: null, electricChars: true, + autoClearEmptyLines: false, onKeyEvent: null, + onDragEvent: null, lineWrapping: false, lineNumbers: false, gutter: false, fixedGutter: false, firstLineNumber: 1, readOnly: false, + dragDrop: true, onChange: null, onCursorActivity: null, + onViewportChange: null, onGutterClick: null, - onHighlightComplete: null, onUpdate: null, onFocus: null, onBlur: null, onScroll: null, matchBrackets: false, + cursorBlinkRate: 530, workTime: 100, workDelay: 200, pollInterval: 100, undoDepth: 40, tabindex: null, - document: window.document + autofocus: null, + lineNumberFormatter: function(integer) { return integer; } }; var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); @@ -1788,27 +2033,37 @@ var CodeMirror = (function() { var win = /Win/.test(navigator.platform); // Known modes, by name and by MIME - var modes = {}, mimeModes = {}; + var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; CodeMirror.defineMode = function(name, mode) { if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; + if (arguments.length > 2) { + mode.dependencies = []; + for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]); + } modes[name] = mode; }; CodeMirror.defineMIME = function(mime, spec) { mimeModes[mime] = spec; }; - CodeMirror.getMode = function(options, spec) { + CodeMirror.resolveMode = function(spec) { if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) spec = mimeModes[spec]; - if (typeof spec == "string") - var mname = spec, config = {}; - else if (spec != null) - var mname = spec.name, config = spec; - var mfactory = modes[mname]; - if (!mfactory) { - if (window.console) console.warn("No mode " + mname + " found, falling back to plain text."); - return CodeMirror.getMode(options, "text/plain"); + else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) + return CodeMirror.resolveMode("application/xml"); + if (typeof spec == "string") return {name: spec}; + else return spec || {name: "null"}; + }; + CodeMirror.getMode = function(options, spec) { + var spec = CodeMirror.resolveMode(spec); + var mfactory = modes[spec.name]; + if (!mfactory) return CodeMirror.getMode(options, "text/plain"); + var modeObj = mfactory(options, spec); + if (modeExtensions.hasOwnProperty(spec.name)) { + var exts = modeExtensions[spec.name]; + for (var prop in exts) if (exts.hasOwnProperty(prop)) modeObj[prop] = exts[prop]; } - return mfactory(options, config || {}); + modeObj.name = spec.name; + return modeObj; }; CodeMirror.listModes = function() { var list = []; @@ -1828,6 +2083,16 @@ var CodeMirror = (function() { extensions[name] = func; }; + var initHooks = []; + CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; + + var modeExtensions = CodeMirror.modeExtensions = {}; + CodeMirror.extendMode = function(mode, properties) { + var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); + for (var prop in properties) if (properties.hasOwnProperty(prop)) + exts[prop] = properties[prop]; + }; + var commands = CodeMirror.commands = { selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});}, killLine: function(cm) { @@ -1865,6 +2130,10 @@ var CodeMirror = (function() { indentMore: function(cm) {cm.indentSelection("add");}, indentLess: function(cm) {cm.indentSelection("subtract");}, insertTab: function(cm) {cm.replaceSelection("\t", "end");}, + defaultTab: function(cm) { + if (cm.somethingSelected()) cm.indentSelection("add"); + else cm.replaceSelection("\t", "end"); + }, transposeChars: function(cm) { var cur = cm.getCursor(), line = cm.getLine(cur.line); if (cur.ch > 0 && cur.ch < line.length - 1) @@ -1882,7 +2151,7 @@ var CodeMirror = (function() { keyMap.basic = { "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", - "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "indentMore", "Shift-Tab": "indentLess", + "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "defaultTab", "Shift-Tab": "indentAuto", "Enter": "newlineAndIndent", "Insert": "toggleOverwrite" }; // Note that the save and find-related commands aren't defined by @@ -1893,6 +2162,7 @@ var CodeMirror = (function() { "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", "Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "Ctrl-S": "save", "Ctrl-F": "find", "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", fallthrough: "basic" }; keyMap.macDefault = { @@ -1901,6 +2171,7 @@ var CodeMirror = (function() { "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft", "Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "Cmd-S": "save", "Cmd-F": "find", "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", fallthrough: ["basic", "emacsy"] }; keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; @@ -1911,31 +2182,55 @@ var CodeMirror = (function() { "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" }; - function lookupKey(name, extraMap, map) { - function lookup(name, map, ft) { + function getKeyMap(val) { + if (typeof val == "string") return keyMap[val]; + else return val; + } + function lookupKey(name, extraMap, map, handle, stop) { + function lookup(map) { + map = getKeyMap(map); var found = map[name]; - if (found != null) return found; - if (ft == null) ft = map.fallthrough; - if (ft == null) return map.catchall; - if (typeof ft == "string") return lookup(name, keyMap[ft]); - for (var i = 0, e = ft.length; i < e; ++i) { - found = lookup(name, keyMap[ft[i]]); - if (found != null) return found; + if (found === false) { + if (stop) stop(); + return true; } - return null; + if (found != null && handle(found)) return true; + if (map.nofallthrough) { + if (stop) stop(); + return true; + } + var fallthrough = map.fallthrough; + if (fallthrough == null) return false; + if (Object.prototype.toString.call(fallthrough) != "[object Array]") + return lookup(fallthrough); + for (var i = 0, e = fallthrough.length; i < e; ++i) { + if (lookup(fallthrough[i])) return true; + } + return false; } - return extraMap ? lookup(name, extraMap, map) : lookup(name, keyMap[map]); + if (extraMap && lookup(extraMap)) return true; + return lookup(map); } function isModifierKey(event) { var name = keyNames[e_prop(event, "keyCode")]; return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; } + CodeMirror.isModifierKey = isModifierKey; CodeMirror.fromTextArea = function(textarea, options) { if (!options) options = {}; options.value = textarea.value; if (!options.tabindex && textarea.tabindex) options.tabindex = textarea.tabindex; + // Set autofocus to true if this textarea is focused, or if it has + // autofocus and no other element is focused. + if (options.autofocus == null) { + var hasFocus = document.body; + // doc.activeElement occasionally throws on IE + try { hasFocus = document.activeElement; } catch(e) {} + options.autofocus = hasFocus == textarea || + textarea.getAttribute("autofocus") != null && hasFocus == document.body; + } function save() {textarea.value = instance.getValue();} if (textarea.form) { @@ -1943,13 +2238,12 @@ var CodeMirror = (function() { var rmSubmit = connect(textarea.form, "submit", save, true); if (typeof textarea.form.submit == "function") { var realSubmit = textarea.form.submit; - function wrappedSubmit() { + textarea.form.submit = function wrappedSubmit() { save(); textarea.form.submit = realSubmit; textarea.form.submit(); textarea.form.submit = wrappedSubmit; - } - textarea.form.submit = wrappedSubmit; + }; } } @@ -1972,6 +2266,18 @@ var CodeMirror = (function() { return instance; }; + var gecko = /gecko\/\d{7}/i.test(navigator.userAgent); + var ie = /MSIE \d/.test(navigator.userAgent); + var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent); + var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent); + var quirksMode = ie && document.documentMode == 5; + var webkit = /WebKit\//.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 10\D([7-9]|\d\d)\D/.test(navigator.userAgent); + // Utility functions for working with state. Exported because modes // sometimes need to do this. function copyState(mode, state) { @@ -1990,6 +2296,14 @@ var CodeMirror = (function() { return mode.startState ? mode.startState(a1, a2) : true; } CodeMirror.startState = startState; + CodeMirror.innerMode = function(mode, state) { + while (mode.innerMode) { + var info = mode.innerMode(state); + state = info.state; + mode = info.mode; + } + return info || {mode: mode, state: state}; + }; // The character stream used by a mode's parser. function StringStream(string, tabSize) { @@ -2000,7 +2314,7 @@ var CodeMirror = (function() { StringStream.prototype = { eol: function() {return this.pos >= this.string.length;}, sol: function() {return this.pos == 0;}, - peek: function() {return this.string.charAt(this.pos);}, + peek: function() {return this.string.charAt(this.pos) || undefined;}, next: function() { if (this.pos < this.string.length) return this.string.charAt(this.pos++); @@ -2031,14 +2345,14 @@ var CodeMirror = (function() { indentation: function() {return countColumn(this.string, null, this.tabSize);}, match: function(pattern, consume, caseInsensitive) { if (typeof pattern == "string") { - function cased(str) {return caseInsensitive ? str.toLowerCase() : str;} + var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { if (consume !== false) this.pos += pattern.length; return true; } - } - else { + } else { var match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) return null; if (match && consume !== false) this.pos += match[0].length; return match; } @@ -2047,200 +2361,174 @@ var CodeMirror = (function() { }; CodeMirror.StringStream = StringStream; - function MarkedText(from, to, className, set) { - this.from = from; this.to = to; this.style = className; this.set = set; + function MarkedSpan(from, to, marker) { + this.from = from; this.to = to; this.marker = marker; } - MarkedText.prototype = { - attach: function(line) { this.set.push(line); }, - detach: function(line) { - var ix = indexOf(this.set, line); - if (ix > -1) this.set.splice(ix, 1); - }, - split: function(pos, lenBefore) { - if (this.to <= pos && this.to != null) return null; - var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore; - var to = this.to == null ? null : this.to - pos + lenBefore; - return new MarkedText(from, to, this.style, this.set); - }, - dup: function() { return new MarkedText(null, null, this.style, this.set); }, - clipTo: function(fromOpen, from, toOpen, to, diff) { - if (this.from != null && this.from >= from) - this.from = Math.max(to, this.from) + diff; - if (this.to != null && this.to > from) - this.to = to < this.to ? this.to + diff : from; - if (fromOpen && to > this.from && (to < this.to || this.to == null)) - this.from = null; - if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null)) - this.to = null; - }, - isDead: function() { return this.from != null && this.to != null && this.from >= this.to; }, - sameSet: function(x) { return this.set == x.set; } - }; - function Bookmark(pos) { - this.from = pos; this.to = pos; this.line = null; + function getMarkedSpanFor(spans, marker) { + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.marker == marker) return span; + } } - Bookmark.prototype = { - attach: function(line) { this.line = line; }, - detach: function(line) { if (this.line == line) this.line = null; }, - split: function(pos, lenBefore) { - if (pos < this.from) { - this.from = this.to = (this.from - pos) + lenBefore; - return this; - } - }, - isDead: function() { return this.from > this.to; }, - clipTo: function(fromOpen, from, toOpen, to, diff) { - if ((fromOpen || from < this.from) && (toOpen || to > this.to)) { - this.from = 0; this.to = -1; - } else if (this.from > from) { - this.from = this.to = Math.max(to, this.from) + diff; - } - }, - sameSet: function(x) { return false; }, - find: function() { - if (!this.line || !this.line.parent) return null; - return {line: lineNo(this.line), ch: this.from}; - }, - clear: function() { - if (this.line) { - var found = indexOf(this.line.marked, this); - if (found != -1) this.line.marked.splice(found, 1); - this.line = null; + + function removeMarkedSpan(spans, span) { + var r; + for (var i = 0; i < spans.length; ++i) + if (spans[i] != span) (r || (r = [])).push(spans[i]); + return r; + } + + function markedSpansBefore(old, startCh, endCh) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); + if (startsBefore || marker.type == "bookmark" && span.from == startCh && span.from != endCh) { + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); + (nw || (nw = [])).push({from: span.from, + to: endsAfter ? null : span.to, + marker: marker}); } } - }; + return nw; + } + + function markedSpansAfter(old, endCh) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); + if (endsAfter || marker.type == "bookmark" && span.from == endCh) { + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); + (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh, + to: span.to == null ? null : span.to - endCh, + marker: marker}); + } + } + return nw; + } + + function updateMarkedSpans(oldFirst, oldLast, startCh, endCh, newText) { + if (!oldFirst && !oldLast) return newText; + // Get the spans that 'stick out' on both sides + var first = markedSpansBefore(oldFirst, startCh); + var last = markedSpansAfter(oldLast, endCh); + + // Next, merge those two ends + var sameLine = newText.length == 1, offset = lst(newText).length + (sameLine ? startCh : 0); + if (first) { + // Fix up .to properties of first + for (var i = 0; i < first.length; ++i) { + var span = first[i]; + if (span.to == null) { + var found = getMarkedSpanFor(last, span.marker); + if (!found) span.to = startCh; + else if (sameLine) span.to = found.to == null ? null : found.to + offset; + } + } + } + if (last) { + // Fix up .from in last (or move them into first in case of sameLine) + for (var i = 0; i < last.length; ++i) { + var span = last[i]; + if (span.to != null) span.to += offset; + if (span.from == null) { + var found = getMarkedSpanFor(first, span.marker); + if (!found) { + span.from = offset; + if (sameLine) (first || (first = [])).push(span); + } + } else { + span.from += offset; + if (sameLine) (first || (first = [])).push(span); + } + } + } + + var newMarkers = [newHL(newText[0], first)]; + if (!sameLine) { + // Fill gap with whole-line-spans + var gap = newText.length - 2, gapMarkers; + if (gap > 0 && first) + for (var i = 0; i < first.length; ++i) + if (first[i].to == null) + (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker}); + for (var i = 0; i < gap; ++i) + newMarkers.push(newHL(newText[i+1], gapMarkers)); + newMarkers.push(newHL(lst(newText), last)); + } + return newMarkers; + } + + // hl stands for history-line, a data structure that can be either a + // string (line without markers) or a {text, markedSpans} object. + function hlText(val) { return typeof val == "string" ? val : val.text; } + function hlSpans(val) { + if (typeof val == "string") return null; + var spans = val.markedSpans, out = null; + for (var i = 0; i < spans.length; ++i) { + if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); } + else if (out) out.push(spans[i]); + } + return !out ? spans : out.length ? out : null; + } + function newHL(text, spans) { return spans ? {text: text, markedSpans: spans} : text; } + + function detachMarkedSpans(line) { + var spans = line.markedSpans; + if (!spans) return; + for (var i = 0; i < spans.length; ++i) { + var lines = spans[i].marker.lines; + var ix = indexOf(lines, line); + lines.splice(ix, 1); + } + line.markedSpans = null; + } + + function attachMarkedSpans(line, spans) { + if (!spans) return; + for (var i = 0; i < spans.length; ++i) + var marker = spans[i].marker.lines.push(line); + line.markedSpans = spans; + } + + // When measuring the position of the end of a line, different + // browsers require different approaches. If an empty span is added, + // many browsers report bogus offsets. Of those, some (Webkit, + // recent IE) will accept a space without moving the whole span to + // the next line when wrapping it, others work with a zero-width + // space. + var eolSpanContent = " "; + if (gecko || (ie && !ie_lt8)) eolSpanContent = "\u200b"; + else if (opera) eolSpanContent = ""; // Line objects. These hold state related to a line, including // highlighting info (the styles array). - function Line(text, styles) { - this.styles = styles || [text, null]; + function Line(text, markedSpans) { this.text = text; this.height = 1; - this.marked = this.gutterMarker = this.className = this.handlers = null; - this.stateAfter = this.parent = this.hidden = null; - } - Line.inheritMarks = function(text, orig) { - var ln = new Line(text), mk = orig && orig.marked; - if (mk) { - for (var i = 0; i < mk.length; ++i) { - if (mk[i].to == null && mk[i].style) { - var newmk = ln.marked || (ln.marked = []), mark = mk[i]; - var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln); - } - } - } - return ln; + attachMarkedSpans(this, markedSpans); } Line.prototype = { - // Replace a piece of a line, keeping the styles around it intact. - replace: function(from, to_, text) { - var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_; - copyStyles(0, from, this.styles, st); - if (text) st.push(text, null); - copyStyles(to, this.text.length, this.styles, st); - this.styles = st; - this.text = this.text.slice(0, from) + text + this.text.slice(to); - this.stateAfter = null; - if (mk) { - var diff = text.length - (to - from); - for (var i = 0; i < mk.length; ++i) { - var mark = mk[i]; - mark.clipTo(from == null, from || 0, to_ == null, to, diff); - if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);} - } - } - }, - // Split a part off a line, keeping styles and markers intact. - split: function(pos, textBefore) { - var st = [textBefore, null], mk = this.marked; - copyStyles(pos, this.text.length, this.styles, st); - var taken = new Line(textBefore + this.text.slice(pos), st); - if (mk) { - for (var i = 0; i < mk.length; ++i) { - var mark = mk[i]; - var newmark = mark.split(pos, textBefore.length); - if (newmark) { - if (!taken.marked) taken.marked = []; - taken.marked.push(newmark); newmark.attach(taken); - } - } - } - return taken; - }, - append: function(line) { - var mylen = this.text.length, mk = line.marked, mymk = this.marked; - this.text += line.text; - copyStyles(0, line.text.length, line.styles, this.styles); - if (mymk) { - for (var i = 0; i < mymk.length; ++i) - if (mymk[i].to == null) mymk[i].to = mylen; - } - if (mk && mk.length) { - if (!mymk) this.marked = mymk = []; - outer: for (var i = 0; i < mk.length; ++i) { - var mark = mk[i]; - if (!mark.from) { - for (var j = 0; j < mymk.length; ++j) { - var mymark = mymk[j]; - if (mymark.to == mylen && mymark.sameSet(mark)) { - mymark.to = mark.to == null ? null : mark.to + mylen; - if (mymark.isDead()) { - mymark.detach(this); - mk.splice(i--, 1); - } - continue outer; - } - } - } - mymk.push(mark); - mark.attach(this); - mark.from += mylen; - if (mark.to != null) mark.to += mylen; - } - } - }, - fixMarkEnds: function(other) { - var mk = this.marked, omk = other.marked; - if (!mk) return; - for (var i = 0; i < mk.length; ++i) { - var mark = mk[i], close = mark.to == null; - if (close && omk) { - for (var j = 0; j < omk.length; ++j) - if (omk[j].sameSet(mark)) {close = false; break;} - } - if (close) mark.to = this.text.length; - } - }, - fixMarkStarts: function() { - var mk = this.marked; - if (!mk) return; - for (var i = 0; i < mk.length; ++i) - if (mk[i].from == null) mk[i].from = 0; - }, - addMark: function(mark) { - mark.attach(this); - if (this.marked == null) this.marked = []; - this.marked.push(mark); - this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);}); + update: function(text, markedSpans) { + this.text = text; + this.stateAfter = this.styles = null; + detachMarkedSpans(this); + attachMarkedSpans(this, markedSpans); }, // Run the given mode's parser over a line, update the styles // array, which contains alternating fragments of text and CSS // classes. highlight: function(mode, state, tabSize) { - var stream = new StringStream(this.text, tabSize), st = this.styles, pos = 0; - var changed = false, curWord = st[0], prevWord; + var stream = new StringStream(this.text, tabSize), st = this.styles || (this.styles = []); + var pos = st.length = 0; if (this.text == "" && mode.blankLine) mode.blankLine(state); while (!stream.eol()) { - var style = mode.token(stream, state); - var substr = this.text.slice(stream.start, stream.pos); + var style = mode.token(stream, state), substr = stream.current(); stream.start = stream.pos; - if (pos && st[pos-1] == style) + if (pos && st[pos-1] == style) { st[pos-2] += substr; - else if (substr) { - if (!changed && (st[pos+1] != style || (pos && st[pos-2] != prevWord))) changed = true; + } else if (substr) { st[pos++] = substr; st[pos++] = style; - prevWord = curWord; curWord = st[pos]; } // Give up when line is ridiculously long if (stream.pos > 5000) { @@ -2248,17 +2536,19 @@ var CodeMirror = (function() { break; } } - if (st.length != pos) {st.length = pos; changed = true;} - if (pos && st[pos-2] != prevWord) changed = true; - // Short lines with simple highlights return null, and are - // counted as changed by the driver because they are likely to - // highlight the same way in various contexts. - return changed || (st.length < 5 && this.text.length < 10 ? null : false); + }, + process: function(mode, state, tabSize) { + var stream = new StringStream(this.text, tabSize); + if (this.text == "" && mode.blankLine) mode.blankLine(state); + while (!stream.eol() && stream.pos <= 5000) { + mode.token(stream, state); + stream.start = stream.pos; + } }, // Fetch the parser token for a given character. Useful for hacks // that want to inspect the mode state (say, for completion). - getTokenAt: function(mode, state, ch) { - var txt = this.text, stream = new StringStream(txt); + getTokenAt: function(mode, state, tabSize, ch) { + var txt = this.text, stream = new StringStream(txt, tabSize); while (stream.pos < ch && !stream.eol()) { stream.start = stream.pos; var style = mode.token(stream, state); @@ -2272,50 +2562,112 @@ var CodeMirror = (function() { indentation: function(tabSize) {return countColumn(this.text, null, tabSize);}, // Produces an HTML fragment for the line, taking selection, // marking, and highlighting into account. - getHTML: function(tabText, endAt) { - var html = [], first = true; - function span(text, style) { + getContent: function(tabSize, wrapAt, compensateForWrapping) { + var first = true, col = 0, specials = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g; + var pre = elt("pre"); + function span_(html, text, style) { if (!text) return; // Work around a bug where, in some compat modes, IE ignores leading spaces if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1); first = false; - if (style) html.push('', htmlEscape(text).replace(/\t/g, tabText), ""); - else html.push(htmlEscape(text).replace(/\t/g, tabText)); + if (!specials.test(text)) { + col += text.length; + var content = document.createTextNode(text); + } else { + var content = document.createDocumentFragment(), pos = 0; + while (true) { + specials.lastIndex = pos; + var m = specials.exec(text); + var skipped = m ? m.index - pos : text.length - pos; + if (skipped) { + content.appendChild(document.createTextNode(text.slice(pos, pos + skipped))); + col += skipped; + } + if (!m) break; + pos += skipped + 1; + if (m[0] == "\t") { + var tabWidth = tabSize - col % tabSize; + content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); + col += tabWidth; + } else { + var token = elt("span", "\u2022", "cm-invalidchar"); + token.title = "\\u" + m[0].charCodeAt(0).toString(16); + content.appendChild(token); + col += 1; + } + } + } + if (style) html.appendChild(elt("span", [content], style)); + else html.appendChild(content); } - var st = this.styles, allText = this.text, marked = this.marked; + var span = span_; + if (wrapAt != null) { + var outPos = 0, anchor = pre.anchor = elt("span"); + span = function(html, text, style) { + var l = text.length; + if (wrapAt >= outPos && wrapAt < outPos + l) { + var cut = wrapAt - outPos; + if (cut) { + span_(html, text.slice(0, cut), style); + // See comment at the definition of spanAffectsWrapping + if (compensateForWrapping) { + var view = text.slice(cut - 1, cut + 1); + if (spanAffectsWrapping.test(view)) html.appendChild(elt("wbr")); + else if (!ie_lt8 && /\w\w/.test(view)) html.appendChild(document.createTextNode("\u200d")); + } + } + html.appendChild(anchor); + span_(anchor, opera ? text.slice(cut, cut + 1) : text.slice(cut), style); + if (opera) span_(html, text.slice(cut + 1), style); + wrapAt--; + outPos += l; + } else { + outPos += l; + span_(html, text, style); + if (outPos == wrapAt && outPos == len) { + setTextContent(anchor, eolSpanContent); + html.appendChild(anchor); + } + // Stop outputting HTML when gone sufficiently far beyond measure + else if (outPos > wrapAt + 10 && /\s/.test(text)) span = function(){}; + } + }; + } + + var st = this.styles, allText = this.text, marked = this.markedSpans; var len = allText.length; - if (endAt != null) len = Math.min(endAt, len); function styleToClass(style) { if (!style) return null; return "cm-" + style.replace(/ +/g, " cm-"); } - - if (!allText && endAt == null) - span(" "); - else if (!marked || !marked.length) + if (!allText && wrapAt == null) { + span(pre, " "); + } else if (!marked || !marked.length) { for (var i = 0, ch = 0; ch < len; i+=2) { var str = st[i], style = st[i+1], l = str.length; if (ch + l > len) str = str.slice(0, len - ch); ch += l; - span(str, styleToClass(style)); + span(pre, str, styleToClass(style)); } - else { + } else { + marked.sort(function(a, b) { return a.from - b.from; }); var pos = 0, i = 0, text = "", style, sg = 0; var nextChange = marked[0].from || 0, marks = [], markpos = 0; - function advanceMarks() { + var advanceMarks = function() { var m; while (markpos < marked.length && ((m = marked[markpos]).from == pos || m.from == null)) { - if (m.style != null) marks.push(m); + if (m.marker.type == "range") marks.push(m); ++markpos; } nextChange = markpos < marked.length ? marked[markpos].from : Infinity; for (var i = 0; i < marks.length; ++i) { - var to = marks[i].to || Infinity; + var to = marks[i].to; + if (to == null) to = Infinity; if (to == pos) marks.splice(i--, 1); else nextChange = Math.min(to, nextChange); } - } + }; var m = 0; while (pos < len) { if (nextChange == pos) advanceMarks(); @@ -2324,9 +2676,13 @@ var CodeMirror = (function() { if (text) { var end = pos + text.length; var appliedStyle = style; - for (var j = 0; j < marks.length; ++j) - appliedStyle = (appliedStyle ? appliedStyle + " " : "") + marks[j].style; - span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle); + for (var j = 0; j < marks.length; ++j) { + var mark = marks[j]; + appliedStyle = (appliedStyle ? appliedStyle + " " : "") + mark.marker.style; + if (mark.marker.endStyle && mark.to === Math.min(end, upto)) appliedStyle += " " + mark.marker.endStyle; + if (mark.marker.startStyle && mark.from === pos) appliedStyle += " " + mark.marker.startStyle; + } + span(pre, end > upto ? text.slice(0, upto - pos) : text, appliedStyle); if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} pos = end; } @@ -2334,29 +2690,13 @@ var CodeMirror = (function() { } } } - return html.join(""); + return pre; }, cleanUp: function() { this.parent = null; - if (this.marked) - for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this); + detachMarkedSpans(this); } }; - // Utility used by replace and split above - function copyStyles(from, to, source, dest) { - for (var i = 0, pos = 0, state = 0; pos < to; i+=2) { - var part = source[i], end = pos + part.length; - if (state == 0) { - if (end > from) dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i+1]); - if (end >= from) state = 1; - } - else if (state == 1) { - if (end > to) dest.push(part.slice(0, to - pos), source[i+1]); - else dest.push(part, source[i+1]); - } - pos = end; - } - } // Data structure that holds the sequence of lines. function LeafChunk(lines) { @@ -2385,7 +2725,7 @@ var CodeMirror = (function() { }, insertHeight: function(at, lines, height) { this.height += height; - this.lines.splice.apply(this.lines, [at, 0].concat(lines)); + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this; }, iterN: function(at, n, op) { @@ -2551,33 +2891,36 @@ var CodeMirror = (function() { function History() { this.time = 0; this.done = []; this.undone = []; + this.compound = 0; + this.closed = false; } History.prototype = { addChange: function(start, added, old) { this.undone.length = 0; - var time = +new Date, cur = this.done[this.done.length - 1], last = cur && cur[cur.length - 1]; + var time = +new Date, cur = lst(this.done), last = cur && lst(cur); var dtime = time - this.time; - if (dtime > 400 || !last) { - this.done.push([{start: start, added: added, old: old}]); - } else if (last.start > start + added || last.start + last.added < start - last.added + last.old.length) { + + if (cur && !this.closed && this.compound) { cur.push({start: start, added: added, old: old}); + } else if (dtime > 400 || !last || this.closed || + last.start > start + old.length || last.start + last.added < start) { + this.done.push([{start: start, added: added, old: old}]); + this.closed = false; } else { - var oldoff = 0; - if (start < last.start) { - for (var i = last.start - start - 1; i >= 0; --i) - last.old.unshift(old[i]); - last.added += last.start - start; - last.start = start; - } - else if (last.start < start) { - oldoff = start - last.start; - added += oldoff; - } - for (var i = last.added - oldoff, e = old.length; i < e; ++i) - last.old.push(old[i]); - if (last.added < added) last.added = added; + var startBefore = Math.max(0, last.start - start), + endAfter = Math.max(0, (start + old.length) - (last.start + last.added)); + for (var i = startBefore; i > 0; --i) last.old.unshift(old[i - 1]); + for (var i = endAfter; i > 0; --i) last.old.push(old[old.length - i]); + if (startBefore) last.start = start; + last.added += added - (old.length - startBefore - endAfter); } this.time = time; + }, + startCompound: function() { + if (!this.compound++) this.closed = true; + }, + endCompound: function() { + if (!--this.compound) this.closed = true; } }; @@ -2603,10 +2946,14 @@ var CodeMirror = (function() { function e_target(e) {return e.target || e.srcElement;} function e_button(e) { - if (e.which) return e.which; - else if (e.button & 1) return 1; - else if (e.button & 2) return 3; - else if (e.button & 4) return 2; + var b = e.which; + if (b == null) { + if (e.button & 1) b = 1; + else if (e.button & 2) b = 3; + else if (e.button & 4) b = 2; + } + if (mac && e.ctrlKey && b == 1) b = 3; + return b; } // Allow 3rd-party code to override event properties by adding an override @@ -2622,8 +2969,7 @@ var CodeMirror = (function() { if (typeof node.addEventListener == "function") { node.addEventListener(type, handler, false); if (disconnect) return function() {node.removeEventListener(type, handler, false);}; - } - else { + } else { var wrapHandler = function(event) {handler(event || window.event);}; node.attachEvent("on" + type, wrapHandler); if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);}; @@ -2634,26 +2980,36 @@ var CodeMirror = (function() { function Delayed() {this.id = null;} Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}}; + var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; + // Detect drag-and-drop var dragAndDrop = function() { - // IE8 has ondragstart and ondrop properties, but doesn't seem to - // actually support ondragstart the way it's supposed to work. - if (/MSIE [1-8]\b/.test(navigator.userAgent)) return false; - var div = document.createElement('div'); - return "draggable" in div; + // There is *some* kind of drag-and-drop support in IE6-8, but I + // couldn't get it to work yet. + if (ie_lt9) return false; + var div = elt('div'); + return "draggable" in div || "dragDrop" in div; }(); - var gecko = /gecko\/\d{7}/i.test(navigator.userAgent); - var ie = /MSIE \d/.test(navigator.userAgent); - var webkit = /WebKit\//.test(navigator.userAgent); - - var lineSep = "\n"; // Feature-detect whether newlines in textareas are converted to \r\n - (function () { - var te = document.createElement("textarea"); + var lineSep = function () { + var te = elt("textarea"); te.value = "foo\nbar"; - if (te.value.indexOf("\r") > -1) lineSep = "\r\n"; - }()); + if (te.value.indexOf("\r") > -1) return "\r\n"; + return "\n"; + }(); + + // For a reason I have yet to figure out, some browsers disallow + // word wrapping between certain characters *only* if a new inline + // element is started between them. This makes it hard to reliably + // measure the position of things, since that requires inserting an + // extra span. This terribly fragile set of regexps matches the + // character combinations that suffer from this phenomenon on the + // various browsers. + var spanAffectsWrapping = /^$/; // Won't match any two-character string + if (gecko) spanAffectsWrapping = /$'/; + else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/; + else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/; // Counts the column offset in a string, taking tabs into account. // Used mostly to find indentation. @@ -2669,31 +3025,7 @@ var CodeMirror = (function() { return n; } - function computedStyle(elt) { - if (elt.currentStyle) return elt.currentStyle; - return window.getComputedStyle(elt, null); - } - - // Find the position of an element by following the offsetParent chain. - // If screen==true, it returns screen (rather than page) coordinates. function eltOffset(node, screen) { - var bod = node.ownerDocument.body; - var x = 0, y = 0, skipBody = false; - for (var n = node; n; n = n.offsetParent) { - var ol = n.offsetLeft, ot = n.offsetTop; - // Firefox reports weird inverted offsets when the body has a border. - if (n == bod) { x += Math.abs(ol); y += Math.abs(ot); } - else { x += ol, y += ot; } - if (screen && computedStyle(n).position == "fixed") - skipBody = true; - } - var e = screen && !skipBody ? null : bod; - for (var n = node.parentNode; n != e; n = n.parentNode) - if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;} - return {left: x, top: y}; - } - // Use the faster and saner getBoundingClientRect method when possible. - if (document.documentElement.getBoundingClientRect != null) eltOffset = function(node, screen) { // Take the parts of bounding client rect that we are interested in so we are able to edit if need be, // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page) try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; } @@ -2709,12 +3041,21 @@ var CodeMirror = (function() { } } return box; - }; + } - // Get a node's text content. function eltText(node) { return node.textContent || node.innerText || node.nodeValue || ""; } + + var spaceStrs = [""]; + function spaceStr(n) { + while (spaceStrs.length <= n) + spaceStrs.push(lst(spaceStrs) + " "); + return spaceStrs[n]; + } + + function lst(arr) { return arr[arr.length-1]; } + function selectInput(node) { if (ios) { // Mobile Safari apparently has a bug where select() is broken. node.selectionStart = 0; @@ -2727,26 +3068,27 @@ var CodeMirror = (function() { function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);} function copyPos(x) {return {line: x.line, ch: x.ch};} - var escapeElement = document.createElement("pre"); - function htmlEscape(str) { - escapeElement.textContent = str; - return escapeElement.innerHTML; + function elt(tag, content, className, style) { + var e = document.createElement(tag); + if (className) e.className = className; + if (style) e.style.cssText = style; + if (typeof content == "string") setTextContent(e, content); + else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); + return e; + } + function removeChildren(e) { + e.innerHTML = ""; + return e; + } + function removeChildrenAndAdd(parent, e) { + removeChildren(parent).appendChild(e); + } + function setTextContent(e, str) { + if (ie_lt9) { + e.innerHTML = ""; + e.appendChild(document.createTextNode(str)); + } else e.textContent = str; } - // Recent (late 2011) Opera betas insert bogus newlines at the start - // of the textContent, so we strip those. - if (htmlEscape("a") == "\na") - htmlEscape = function(str) { - escapeElement.textContent = str; - return escapeElement.innerHTML.slice(1); - }; - // Some IEs don't preserve tabs through innerHTML - else if (htmlEscape("\t") != "\t") - htmlEscape = function(str) { - escapeElement.innerHTML = ""; - escapeElement.appendChild(document.createTextNode(str)); - return escapeElement.innerHTML; - }; - CodeMirror.htmlEscape = htmlEscape; // Used to position the cursor after an undo/redo by finding the // last edited character. @@ -2764,21 +3106,31 @@ var CodeMirror = (function() { if (collection[i] == elt) return i; return -1; } + var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc]/; function isWordChar(ch) { - return /\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase(); + return /\w/.test(ch) || ch > "\x80" && + (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); } // See if "".split is the broken IE version, if so, provide an // alternative way to split lines. var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { - var pos = 0, nl, result = []; - while ((nl = string.indexOf("\n", pos)) > -1) { - result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl)); - pos = nl + 1; + var pos = 0, result = [], l = string.length; + while (pos <= l) { + var nl = string.indexOf("\n", pos); + if (nl == -1) nl = string.length; + var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); + var rt = line.indexOf("\r"); + if (rt != -1) { + result.push(line.slice(0, rt)); + pos += rt + 1; + } else { + result.push(line); + pos = nl + 1; + } } - result.push(string.slice(pos)); return result; - } : function(string){return string.split(/\r?\n/);}; + } : function(string){return string.split(/\r\n?|\n/);}; CodeMirror.splitLines = splitLines; var hasSelection = window.getSelection ? function(te) { @@ -2799,10 +3151,10 @@ var CodeMirror = (function() { var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", - 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 186: ";", 187: "=", 188: ",", - 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'", 63276: "PageUp", - 63277: "PageDown", 63275: "End", 63273: "Home", 63234: "Left", 63232: "Up", 63235: "Right", - 63233: "Down", 63302: "Insert", 63272: "Delete"}; + 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete", + 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", + 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home", + 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"}; CodeMirror.keyNames = keyNames; (function() { // Number keys @@ -2813,5 +3165,7 @@ var CodeMirror = (function() { for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; })(); + CodeMirror.version = "2.35 +"; + return CodeMirror; })(); diff --git a/lib/codemirror.min.js b/lib/codemirror.min.js deleted file mode 100644 index 37c06a2..0000000 --- a/lib/codemirror.min.js +++ /dev/null @@ -1 +0,0 @@ -var CodeMirror=function(){function a(d,e){function bS(a){return a>=0&&a
"; - measureText += "x=c.to||b.line e-400&&T(bv.pos,d))return B(a),setTimeout(cx,20),cU(d.line);if(bu&&bu.time>e-400&&T(bu.pos,d))return bv={time:e,pos:d},B(a),cT(d);bu={time:e,pos:d};var g=d,h;if(J&&!f.readOnly&&!T(bs.from,bs.to)&&!U(d,bs.from)&&!U(bs.to,d)){M&&(bf.draggable=!0);var i=H(z,"mouseup",dO(function(b){M&&(bf.draggable=!1),bx=!1,i(),Math.abs(a.clientX-b.clientX)+Math.abs(a.clientY-b.clientY)<10&&(B(b),cL(d.line,d.ch,!0),cx())}),!0);bx=!0;return}B(a),cL(d.line,d.ch,!0);var k=H(z,"mousemove",dO(function(a){clearTimeout(h),B(a),j(a)}),!0),i=H(z,"mouseup",dO(function(a){clearTimeout(h);var b=dB(a);b&&cI(d,b),B(a),cx(),bA=!0,k(),i()}),!0)}function bZ(a){for(var b=E(a);b!=C;b=b.parentNode)if(b.parentNode==be)return B(a);var c=dB(a);if(!c)return;bv={time:+(new Date),pos:c},B(a),cT(c)}function b$(a){a.preventDefault();var b=dB(a,!0),c=a.dataTransfer.files;if(!b||f.readOnly)return;if(c&&c.length&&window.FileReader&&window.File){function d(a,c){var d=new FileReader;d.onload=function(){g[c]=d.result,++h==e&&(b=cN(b),dO(function(){var a=cm(g.join(""),b,b);cI(b,a)})())},d.readAsText(a)}var e=c.length,g=Array(e),h=0;for(var i=0;i -1&&setTimeout(dO(function(){cW(bs.to.line,"smart")}),75)}ct()}function ce(a){if(f.onKeyEvent&&f.onKeyEvent(bT,A(a)))return;G(a,"keyCode")==16&&(bt=null)}function cf(){if(f.readOnly=="nocursor")return;br||(f.onFocus&&f.onFocus(bT),br=!0,C.className.search(/\bCodeMirror-focused\b/)==-1&&(C.className+=" CodeMirror-focused"),bF||cw(!0)),cs(),dD()}function cg(){br&&(f.onBlur&&f.onBlur(bT),br=!1,bM&&dO(function(){bM&&(bM(),bM=null)})(),C.className=C.className.replace(" CodeMirror-focused","")),clearInterval(bn),setTimeout(function(){br||(bt=null)},150)}function ch(a,b,c,d,e){if(bz)return;if(bQ){var g=[];bp.iter(a.line,b.line+1,function(a){g.push(a.text)}),bQ.addChange(a.line,c.length,g);while(bQ.done.length>f.undoDepth)bQ.done.shift()}cl(a,b,c,d,e)}function ci(a,b,c){var d=a.pop(),e=d?d.length:0,f=[];for(var g=c>0?0:e-1,h=c>0?e:-1;g!=h;g+=c){var i=d[g],j=[],k=i.start+i.added;bp.iter(i.start,k,function(a){j.push(a.text)}),f.push({start:i.start,added:i.old.length,old:j});var l=cN({line:i.start+i.old.length-1,ch:Y(j[j.length-1],i.old[i.old.length-1])});cl({line:i.start,ch:0},{line:k-1,ch:bU(k-1).text.length},i.old,l,l)}bA=!0,b.push(f)}function cj(){ci(bQ.done,bQ.undone,-1)}function ck(){ci(bQ.undone,bQ.done,1)}function cl(a,b,c,d,e){function y(a){return a<=Math.min(b.line,b.line+s)?a:a+s}if(bz)return;var g=!1,h=bN.length;f.lineWrapping||bp.iter(a.line,b.line,function(a){if(a.text.length==h)return g=!0,!0});if(a.line!=b.line||c.length>1)bG=!0;var i=b.line-a.line,j=bU(a.line),k=bU(b.line);if(a.ch==0&&b.ch==0&&c[c.length-1]==""){var l=[],m=null;a.line?(m=bU(a.line-1),m.fixMarkEnds(k)):k.fixMarkStarts();for(var n=0,o=c.length-1;n 1&&bp.remove(a.line+1,i-1,bH),bp.insert(a.line+1,l)}if(f.lineWrapping){var p=P.clientWidth/dy()-3;bp.iter(a.line,a.line+c.length,function(a){if(a.hidden)return;var b=Math.ceil(a.text.length/p)||1;b!=a.height&&bV(a,b)})}else bp.iter(a.line,n+c.length,function(a){var b=a.text;b.length>h&&(bN=b,h=b.length,bO=null,g=!1)}),g&&(h=0,bN="",bO=null,bp.iter(0,bp.size,function(a){var b=a.text;b.length>h&&(h=b.length,bN=b)}));var r=[],s=c.length-i-1;for(var n=0,t=bq.length;n b.line&&r.push(u+s)}var v=a.line+Math.min(c.length,500);dI(a.line,v),r.push(v),bq=r,dK(100),bC.push({from:a.line,to:b.line+1,diff:s});var w={from:a,to:b,text:c};if(bD){for(var x=bD;x.next;x=x.next);x.next=w}else bD=w;cJ(d,e,y(bs.from.line),y(bs.to.line)),P.clientHeight&&(S.style.height=bp.height*dv()+2*dz()+"px")}function cm(a,b,c){function d(d){if(U(d,b))return d;if(!U(c,d))return e;var f=d.line+a.length-(c.line-b.line)-1,g=d.ch;return d.line==c.line&&(g+=a[a.length-1].length-(c.ch-(c.line==b.line?b.ch:0))),{line:f,ch:g}}b=cN(b),c?c=cN(c):c=b,a=_(a);var e;return co(a,b,c,function(a){return e=a,{from:d(bs.from),to:d(bs.to)}}),e}function cn(a,b){co(_(a),bs.from,bs.to,function(a){return b=="end"?{from:a,to:a}:b=="start"?{from:bs.from,to:bs.from}:{from:bs.from,to:a}})}function co(a,b,c,d){var e=a.length==1?a[0].length+b.ch:a[a.length-1].length,f=d({line:b.line+a.length-1,ch:e});ch(b,c,a,f.from,f.to)}function cp(a,b){var c=a.line,d=b.line;if(c==d)return bU(c).text.slice(a.ch,b.ch);var e=[bU(c).text.slice(a.ch)];return bp.iter(c+1,d,function(a){e.push(a.text)}),e.push(bU(d).text.slice(0,b.ch)),e.join("\n")}function cq(){return cp(bs.from,bs.to)}function cs(){if(cr)return;bl.set(f.pollInterval,function(){dL(),cv(),br&&cs(),dM()})}function ct(){function b(){dL();var c=cv();!c&&!a?(a=!0,bl.set(60,b)):(cr=!1,cs()),dM()}var a=!1;cr=!0,bl.set(20,b)}function cv(){if(bF||!br||ba(O)||f.readOnly)return!1;var a=O.value;if(a==cu)return!1;bt=null;var b=0,c=Math.min(cu.length,a.length);while(b b)&&bh.scrollIntoView()}function cz(){var a=dp(bs.inverted?bs.from:bs.to),b=f.lineWrapping?Math.min(a.x,bf.offsetWidth):a.x;return cA(b,a.y,b,a.yBot)}function cA(a,b,c,d){var e=dA(),g=dz(),h=dv();b+=g,d+=g,a+=e,c+=e;var i=P.clientHeight,j=P.scrollTop,k=!1,l=!0;b j+i&&(P.scrollTop=d+h-i,k=!0);var m=P.clientWidth,n=P.scrollLeft,o=f.fixedGutter?bd.clientWidth:0;return a m+n-3&&(P.scrollLeft=c+10-m,k=!0,c>S.clientWidth&&(l=!1)),k&&f.onScroll&&f.onScroll(bT),l}function cB(){var a=dv(),b=P.scrollTop-dz(),c=Math.max(0,Math.floor(b/a)),d=Math.ceil((b+P.clientHeight)/a);return{from:w(bp,c),to:w(bp,d)}}function cC(a,b){if(!P.clientWidth){bJ=bK=bI=0;return}var c=cB();if(a!==!0&&a.length==0&&c.from>bJ&&c.to e&&bK-e<20&&(e=Math.min(bp.size,bK));var g=a===!0?[]:cD([{from:bJ,to:bK,domStart:0}],a),h=0;for(var i=0;i e&&(j.to=e),j.from>=j.to?g.splice(i--,1):h+=j.to-j.from}if(h==e-d)return;g.sort(function(a,b){return a.domStart-b.domStart});var k=dv(),l=bd.style.display;bj.style.display="none",cE(d,e,g),bj.style.display=bd.style.display="";var m=d!=bJ||e!=bK||bL!=P.clientHeight+k;m&&(bL=P.clientHeight+k),bJ=d,bK=e,bI=x(bp,d),bc.style.top=bI*k+"px",P.clientHeight&&(S.style.height=bp.height*k+2*dz()+"px");if(bj.childNodes.length!=bK-bJ)throw new Error("BAD PATCH! "+JSON.stringify(g)+" size="+(bK-bJ)+" nodes="+bj.childNodes.length);if(f.lineWrapping){bO=P.clientWidth;var n=bj.firstChild,o=!1;bp.iter(bJ,bK,function(a){if(!a.hidden){var b=Math.round(n.offsetHeight/k)||1;a.height!=b&&(bV(a,b),bG=o=!0)}n=n.nextSibling}),o&&(S.style.height=bp.height*k+2*dz()+"px")}else bO==null&&(bO=dk(bN)),bO>P.clientWidth?(bf.style.width=bO+"px",S.style.width="",S.style.width=P.scrollWidth+"px"):bf.style.width=S.style.width="";return bd.style.display=l,(m||bG)&&cF(),cG(),!b&&f.onUpdate&&f.onUpdate(bT),!0}function cD(a,b){for(var c=0,d=b.length||0;c =j.to?f.push(j):(e.from>j.from&&f.push({from:j.from,to:e.from,domStart:j.domStart}),e.to e)f=d(f),e++;for(var j=0,k=i.to-i.from;j j){if(a.hidden)var b=m.innerHTML="";else{var b=" "+a.getHTML(bP)+"";a.className&&(b='")}m.innerHTML=b,bj.insertBefore(m.firstChild,f)}else f=f.nextSibling;++j})}function cF(){if(!f.gutter&&!f.lineNumbers)return;var a=bc.offsetHeight,b=P.clientHeight;bd.style.height=(a-b<2?b:a)+"px";var c=[],d=bJ;bp.iter(bJ,Math.max(bK,bJ+1),function(a){if(a.hidden)c.push("");else{var b=a.gutterMarker,e=f.lineNumbers?d+f.firstLineNumber:null;b&&b.text?e=b.text.replace("%N%",e!=null?e:""):e==null&&(e="\u00a0"),c.push(b&&b.style?''+b+"':"",bg.firstChild.firstChild.offsetWidth}if(b<=0)return 0;var c=bU(a),d=c.text,f=0,g=0,h=d.length,i,j=Math.min(h,Math.ceil(b/dy()));for(;;){var k=e(j);if(k<=b&&j",e);for(var g=1;g")}++d}),bd.style.display="none",be.innerHTML=c.join("");var e=String(bp.size).length,g=be.firstChild,h=R(g),i="";while(h.length+i.length");c.push(" '}if(bs.from.ch&&b.y>=0){var l=i?bf.clientWidth-c.x:0;k(b.x,b.y,l,e)}var m=Math.max(0,b.y+(bs.from.ch?e:0)),n=Math.min(c.y,bf.clientHeight)-m;n>.2*e&&k(0,m,0,n),(!i||!bs.from.ch)&&c.y c||g>f.text.length)g=f.text.length;return{line:d,ch:g}}d+=b}}var e=bU(a.line);return e.hidden?a.line>=b?d(1)||d(-1):d(-1)||d(1):a}function cL(a,b,c){var d=cN({line:a,ch:b||0});(c?cI:cJ)(d,d)}function cM(a){return Math.max(0,Math.min(a,bp.size-1))}function cN(a){if(a.line<0)return{line:0,ch:0};if(a.line>=bp.size)return{line:bp.size-1,ch:bU(bp.size-1).text.length};var b=a.ch,c=bU(a.line).text.length;return b==null||b>c?{line:a.line,ch:c}:b<0?{line:a.line,ch:0}:a}function cO(a,b){function g(){for(var b=d+a,c=a<0?-1:bp.size;b!=c;b+=a){var e=bU(b);if(!e.hidden)return d=b,f=e,!0}}function h(b){if(e==(a<0?0:f.text.length))if(!b&&g())e=a<0?f.text.length:0;else return!1;else e+=a;return!0}var c=bs.inverted?bs.from:bs.to,d=c.line,e=c.ch,f=bU(d);if(b=="char")h();else if(b=="column")h(!0);else if(b=="word"){var i=!1;for(;;){if(a<0&&!h())break;if($(f.text.charAt(e)))i=!0;else if(i){a<0&&(a=1,h());break}if(a>0&&!h())break}}return{line:d,ch:e}}function cP(a,b){var c=a<0?bs.from:bs.to;if(bt||T(bs.from,bs.to))c=cO(a,b);cL(c.line,c.ch,!0)}function cQ(a,b){T(bs.from,bs.to)?a<0?cm("",cO(a,b),bs.to):cm("",bs.from,cO(a,b)):cm("",bs.from,bs.to),bB=!0}function cS(a,b){var c=0,d=dp(bs.inverted?bs.from:bs.to,!0);cR!=null&&(d.x=cR),b=="page"?c=Math.min(P.clientHeight,window.innerHeight||document.documentElement.clientHeight):b=="line"&&(c=dv());var e=dq(d.x,d.y+c*a+2);cL(e.line,e.ch,!0),cR=d.x}function cT(a){var b=bU(a.line).text,c=a.ch,d=a.ch;while(c>0&&$(b.charAt(c-1)))--c;while(d bN.length&&(bN=a.text)});bC.push({from:0,to:bp.size})}function c$(){for(var a='',b=0;b "}function c_(){bP=c$(),cC(!0)}function da(){P.className=P.className.replace(/\s*cm-s-\w+/g,"")+f.theme.replace(/(^|\s)\s*/g," cm-s-")}function db(){this.set=[]}function dc(a,b,c){function e(a,b,c,e){bU(a).addMark(new o(b,c,e,d.set))}a=cN(a),b=cN(b);var d=new db;if(a.line==b.line)e(a.line,a.ch,b.ch,c);else{e(a.line,a.ch,null,c);for(var f=a.line+1,g=b.line;f i)return h;j=Math.floor(h*.8),k=e(j),kb-g?f:h;var l=Math.ceil((f+h)/2),m=e(l);m>b?(h=l,i=m):(f=l,g=m)}}function dn(a,b){if(b==0)return{top:0,left:0};var c="";if(f.lineWrapping){var d=a.text.indexOf(" ",b+2);c=X(a.text.slice(b+1,d<0?a.text.length:d+(L?5:0)))}bg.innerHTML=" "+a.getHTML(bP,b)+''+X(a.text.charAt(b)||" ")+""+c+"";var e=document.getElementById("CodeMirror-temp-"+dm),g=e.offsetTop,h=e.offsetLeft;if(L&&g==0&&h==0){var i=document.createElement("span");i.innerHTML="x",e.parentNode.insertBefore(i,e.nextSibling),g=i.offsetTop}return{top:g,left:h}}function dp(a,b){var c,d=dv(),e=d*(x(bp,a.line)-(b?bI:0));if(a.ch==0)c=0;else{var g=dn(bU(a.line),a.ch);c=g.left,f.lineWrapping&&(e+=Math.max(0,g.top))}return{x:c,y:e,yBot:e+d}}function dq(a,b){function l(a){var b=dn(h,a);if(j){var d=Math.round(b.top/c);return Math.max(0,b.left+(d-k)*P.clientWidth)}return b.left}b<0&&(b=0);var c=dv(),d=dy(),e=bI+Math.floor(b/c),g=w(bp,e);if(g>=bp.size)return{line:bp.size-1,ch:bU(bp.size-1).text.length};var h=bU(g),i=h.text,j=f.lineWrapping,k=j?e-x(bp,g):0;if(a<=0&&k==0)return{line:g,ch:0};var m=0,n=0,o=i.length,p,q=Math.min(o,Math.ceil((a+k*P.clientWidth*.9)/d));for(;;){var r=l(q);if(r<=a&&qp)return{line:g,ch:o};q=Math.floor(o*.8),r=l(q),ra-n?m:o};var s=Math.ceil((m+o)/2),t=l(s);t>a?(o=s,p=t):(m=s,n=t)}}function dr(a){var b=dp(a,!0),c=Q(bf);return{x:c.left+b.x,y:c.top+b.y,yBot:c.top+b.yBot}}function dv(){if(du==null){du=" ";for(var a=0;a<49;++a)du+="x"}var b=bj.clientHeight;return b==dt?ds:(dt=b,bg.innerHTML=du,ds=bg.firstChild.offsetHeight/50||1,bg.innerHTML="",ds)}function dy(){return P.clientWidth==dx?dw:(dx=P.clientWidth,dw=dk("x"))}function dz(){return bf.offsetTop}function dA(){return bf.offsetLeft}function dB(a,b){var c=Q(P,!0),d,e;try{d=a.clientX,e=a.clientY}catch(a){return null}if(!b&&(d-c.left>P.clientWidth||e-c.top>P.clientHeight))return null;var f=Q(bf,!0);return dq(d-f.left,e-f.top)}function dC(a){function e(){var a=_(O.value).join("\n");a!=d&&dO(cn)(a,"end"),N.style.position="relative",O.style.cssText=c,bF=!1,cw(!0),cs()}var b=dB(a);if(!b||window.opera)return;(T(bs.from,bs.to)||U(b,bs.from)||!U(b,bs.to))&&dO(cL)(b.line,b.ch);var c=O.style.cssText;N.style.position="absolute",O.style.cssText="position: fixed; width: 30px; height: 30px; top: "+(a.clientY-5)+"px; left: "+(a.clientX-5)+"px; z-index: 1000; background: white; "+"border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);",bF=!0;var d=O.value=cq();cx(),O.select();if(K){D(a);var f=H(window,"mouseup",function(){f(),setTimeout(e,20)},!0)}else setTimeout(e,50)}function dD(){clearInterval(bn);var a=!0;bh.style.visibility="",bn=setInterval(function(){bh.style.visibility=(a=!a)?"":"hidden"},650)}function dF(a){function p(a,b,c){if(!a.text)return;var d=a.styles,e=g?0:a.text.length-1,f;for(var i=g?0:d.length-2,j=g?d.length:-2;i!=j;i+=2*h){var k=d[i];if(d[i+1]!=null&&d[i+1]!=m){e+=h*k.length;continue}for(var l=g?0:k.length-1,p=g?k.length:-1;l!=p;l+=h,e+=h)if(e>=b&&e
";du+="x"==g)n.push(f);else{if(n.pop()!=q.charAt(0))return{pos:e,match:!1};if(!n.length)return{pos:e,match:!0}}}}}var b=bs.inverted?bs.from:bs.to,c=bU(b.line),d=b.ch-1,e=d>=0&&dE[c.text.charAt(d)]||dE[c.text.charAt(++d)];if(!e)return;var f=e.charAt(0),g=e.charAt(1)==">",h=g?1:-1,i=c.styles;for(var j=d+1,k=0,l=i.length;k e;--d){if(d==0)return 0;var g=bU(d-1);if(g.stateAfter)return d;var h=g.indentation(f.tabSize);if(c==null||b>h)c=d-1,b=h}return c}function dH(a){var b=dG(a),c=b&&bU(b-1).stateAfter;return c?c=l(bo,c):c=m(bo),bp.iter(b,a,function(a){a.highlight(bo,c,f.tabSize),a.stateAfter=l(bo,c)}),b=bp.size)continue;var d=dG(c),e=d&&bU(d-1).stateAfter;e?e=l(bo,e):e=m(bo);var g=0,h=bo.compareStates,i=!1,j=d,k=!1;bp.iter(j,bp.size,function(b){var d=b.stateAfter;if(+(new Date)>a)return bq.push(j),dK(f.workDelay),i&&bC.push({from:c,to:j+1}),k=!0;var m=b.highlight(bo,e,f.tabSize);m&&(i=!0),b.stateAfter=l(bo,e);if(h){if(d&&h(d,e))return!0}else if(m!==!1||!d)g=0;else if(++g>3&&(!bo.indent||bo.indent(d,"")==bo.indent(e,"")))return!0;++j});if(k)return;i&&bC.push({from:c,to:j+1})}b&&f.onHighlightComplete&&f.onHighlightComplete(bT)}function dK(a){if(!bq.length)return;bm.set(a,dO(dJ))}function dL(){bA=bB=bD=null,bC=[],bE=!1,bH=[]}function dM(){var a=!1,b;bE&&(a=!cz()),bC.length?b=cC(bC,!0):(bE&&cG(),bG&&cF()),a&&cz(),bE&&(cy(),dD()),br&&!bF&&(bA===!0||bA!==!1&&bE)&&cw(bB),bE&&f.matchBrackets&&setTimeout(dO(function(){bM&&(bM(),bM=null),T(bs.from,bs.to)&&dF(!1)}),20);var c=bD,d=bH;bE&&f.onCursorActivity&&f.onCursorActivity(bT),c&&f.onChange&&bT&&f.onChange(bT,c);for(var e=0;e