From c2805e03c84b6e598556fd06d1ede7aaeea7ce9c Mon Sep 17 00:00:00 2001 From: Valerio Virgillito Date: Tue, 6 Mar 2012 16:17:54 -0800 Subject: Squashed commit FileIO-Build-Candidate into Master Fixing issues with HTML and CSS URLs. Adjusted RegEx logic. Also code a mirror update and undo/redo changes were merged into this request. Signed-off-by: Valerio Virgillito --- imports/codemirror/lib/codemirror.js | 554 +++++++++++++++++++++-------------- 1 file changed, 342 insertions(+), 212 deletions(-) mode change 100755 => 100644 imports/codemirror/lib/codemirror.js (limited to 'imports/codemirror/lib/codemirror.js') diff --git a/imports/codemirror/lib/codemirror.js b/imports/codemirror/lib/codemirror.js old mode 100755 new mode 100644 index 79eb457d..9c6e65e4 --- a/imports/codemirror/lib/codemirror.js +++ b/imports/codemirror/lib/codemirror.js @@ -1,4 +1,4 @@ -// CodeMirror version 2.2 +// CodeMirror version 2.22 // // All functions that need access to the editor's state live inside // the CodeMirror function. Below that, at the bottom of the file, @@ -6,7 +6,7 @@ // CodeMirror is the only global var we claim var CodeMirror = (function() { - // This is the function that produces an editor instance. It's + // This is the function that produces an editor instance. Its // closure is used to store the editor state. function CodeMirror(place, givenOptions) { // Determine effective options based on given values and defaults. @@ -22,17 +22,17 @@ var CodeMirror = (function() { // This mess creates the base DOM structure for the editor. wrapper.innerHTML = '
' + // Wraps and hides input textarea - '
' + '
' + '
' + // Set to the height of the text, causes scrolling '
' + // Moved around its parent to cover visible view '
' + // Provides positioning relative to (visible) text origin - '
' + - '
' + + '
' + + '
' + '
 
' + // Absolutely positioned blinky cursor - '
' + // This DIV contains the actual code + '
' + // DIVs containing the selection and the actual code '
'; if (place.appendChild) place.appendChild(wrapper); else place(wrapper); // I've never seen more elegant code in my life. @@ -40,11 +40,13 @@ var CodeMirror = (function() { scroller = wrapper.lastChild, code = scroller.firstChild, mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild, lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild, - cursor = measure.nextSibling, lineDiv = cursor.nextSibling; + cursor = measure.nextSibling, selectionDiv = cursor.nextSibling, + lineDiv = selectionDiv.nextSibling; themeChanged(); // Needed to hide big blue blinking cursor on Mobile Safari - if (/AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent)) input.style.width = "0px"; + if (ios) input.style.width = "0px"; if (!webkit) lineSpace.draggable = true; + lineSpace.style.outline = "none"; if (options.tabindex != null) input.tabIndex = options.tabindex; if (!options.gutter && !options.lineNumbers) gutter.style.display = "none"; @@ -71,7 +73,8 @@ var CodeMirror = (function() { var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false}; // Selection-related flags. shiftSelecting obviously tracks // whether the user is holding shift. - var shiftSelecting, lastClick, lastDoubleClick, draggingText, overwrite = false; + var shiftSelecting, lastClick, lastDoubleClick, lastScrollPos = 0, draggingText, + overwrite = false, suppressEdits = false; // Variables used by startOperation/endOperation to track what // happened during the operation. var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone, @@ -83,7 +86,7 @@ var CodeMirror = (function() { var bracketHighlighted; // Tracks the maximum line length so that the horizontal scrollbar // can be kept static when scrolling. - var maxLine = "", maxWidth, tabText = computeTabText(); + var maxLine = "", maxWidth; // Initialize the content. operation(function(){setValue(options.value || ""); updateInput = false;})(); @@ -99,6 +102,7 @@ var CodeMirror = (function() { // handled in onMouseDown for Gecko. if (!gecko) connect(scroller, "contextmenu", onContextMenu); connect(scroller, "scroll", function() { + lastScrollPos = scroller.scrollTop; updateDisplay([]); if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px"; if (options.onScroll) options.onScroll(instance); @@ -116,7 +120,9 @@ var CodeMirror = (function() { connect(scroller, "drop", operation(onDrop)); connect(scroller, "paste", function(){focusInput(); fastPoll();}); connect(input, "paste", fastPoll); - connect(input, "cut", operation(function(){replaceSelection("");})); + connect(input, "cut", operation(function(){ + if (!options.readOnly) replaceSelection(""); + })); // IE throws unspecified error in certain cases, when // trying to access activeElement before onload @@ -135,23 +141,30 @@ var CodeMirror = (function() { setValue: operation(setValue), getSelection: getSelection, replaceSelection: operation(replaceSelection), - focus: function(){focusInput(); onFocus(); fastPoll();}, + focus: function(){window.focus(); focusInput(); onFocus(); fastPoll();}, setOption: function(option, value) { var oldVal = options[option]; options[option] = value; if (option == "mode" || option == "indentUnit") loadMode(); - else if (option == "readOnly" && value) {onBlur(); input.blur();} + else if (option == "readOnly" && value == "nocursor") {onBlur(); input.blur();} + else if (option == "readOnly" && !value) {resetInput(true);} else if (option == "theme") themeChanged(); else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)(); - else if (option == "tabSize") operation(tabsChanged)(); - if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme") - operation(gutterChanged)(); + else if (option == "tabSize") updateDisplay(true); + if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme") { + gutterChanged(); + updateDisplay(true); + } }, getOption: function(option) {return options[option];}, undo: operation(undo), redo: operation(redo), indentLine: operation(function(n, dir) { - if (isLine(n)) indentLine(n, dir == null ? "smart" : dir ? "add" : "subtract"); + if (typeof dir != "string") { + if (dir == null) dir = options.smartIndent ? "smart" : "prev"; + else dir = dir ? "add" : "subtract"; + } + if (isLine(n)) indentLine(n, dir); }), indentSelection: operation(indentSelected), historySize: function() {return {undo: history.done.length, redo: history.undone.length};}, @@ -248,7 +261,15 @@ var CodeMirror = (function() { moveH: operation(moveH), deleteH: operation(deleteH), moveV: operation(moveV), - toggleOverwrite: function() {overwrite = !overwrite;}, + toggleOverwrite: function() { + if(overwrite){ + overwrite = false; + cursor.className = cursor.className.replace(" CodeMirror-overwrite", ""); + } else { + overwrite = true; + cursor.className += " CodeMirror-overwrite"; + } + }, posFromIndex: function(off) { var lineNo = 0, ch; @@ -268,9 +289,18 @@ var CodeMirror = (function() { }); return index; }, + scrollTo: function(x, y) { + if (x != null) scroller.scrollLeft = x; + if (y != null) scroller.scrollTop = y; + updateDisplay([]); + }, operation: function(f){return operation(f)();}, - refresh: function(){updateDisplay(true);}, + refresh: function(){ + updateDisplay(true); + if (scroller.scrollHeight > lastScrollPos) + scroller.scrollTop = lastScrollPos; + }, getInputField: function(){return input;}, getWrapperElement: function(){return wrapper;}, getScrollerElement: function(){return scroller;}, @@ -297,7 +327,7 @@ var CodeMirror = (function() { } function onMouseDown(e) { - setShift(e.shiftKey); + setShift(e_prop(e, "shiftKey")); // Check whether this is a click in a widget for (var n = e_target(e); n != wrapper; n = n.parentNode) if (n.parentNode == code && n != mover) return; @@ -339,7 +369,7 @@ var CodeMirror = (function() { } else { lastClick = {time: now, pos: start}; } var last = start, going; - if (dragAndDrop && !posEq(sel.from, sel.to) && + if (dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) && !posLess(start, sel.from) && !posLess(sel.to, start)) { // Let the drag handler handle this. if (webkit) lineSpace.draggable = true; @@ -422,10 +452,10 @@ var CodeMirror = (function() { try { var text = e.dataTransfer.getData("Text"); if (text) { - var end = replaceRange(text, pos, pos); - var curFrom = sel.from, curTo = sel.to; - setSelectionUser(pos, end); + var curFrom = sel.from, curTo = sel.to; + setSelectionUser(pos, pos); if (draggingText) replaceRange("", curFrom, curTo); + replaceSelection(text); focusInput(); } } @@ -434,75 +464,103 @@ var CodeMirror = (function() { } function onDragStart(e) { var txt = getSelection(); - // This will reset escapeElement - htmlEscape(txt); - e.dataTransfer.setDragImage(escapeElement, 0, 0); + // Disabled until further notice. Doesn't work on most browsers, + // and crashes Safari (issue #332). + //htmlEscape(txt); + //e.dataTransfer.setDragImage(escapeElement, 0, 0); e.dataTransfer.setData("Text", txt); } - function handleKeyBinding(e) { - var name = keyNames[e.keyCode], next = keyMap[options.keyMap].auto, bound, dropShift; - if (name == null || e.altGraphKey) { - if (next) options.keyMap = next; - return null; - } - if (e.altKey) name = "Alt-" + name; - if (e.ctrlKey) name = "Ctrl-" + name; - if (e.metaKey) name = "Cmd-" + name; - if (e.shiftKey && (bound = lookupKey("Shift-" + name, options.extraKeys, options.keyMap))) { - dropShift = true; - } else { - bound = lookupKey(name, options.extraKeys, options.keyMap); - } + + function doHandleBinding(bound, dropShift) { if (typeof bound == "string") { - if (commands.propertyIsEnumerable(bound)) bound = commands[bound]; - else bound = null; - } - if (next && (bound || !isModifierKey(e))) options.keyMap = next; - if (!bound) return false; - if (dropShift) { - var prevShift = shiftSelecting; - shiftSelecting = null; + bound = commands[bound]; + if (!bound) return false; + } + var prevShift = shiftSelecting; + try { + if (options.readOnly) suppressEdits = true; + if (dropShift) shiftSelecting = null; bound(instance); + } catch(e) { + if (e != Pass) throw e; + return false; + } finally { shiftSelecting = prevShift; - } else bound(instance); - e_preventDefault(e); + suppressEdits = false; + } return true; } - var lastStoppedKey = null; + function handleKeyBinding(e) { + // Handle auto keymap transitions + var startMap = getKeyMap(options.keyMap), next = startMap.auto; + clearTimeout(maybeTransition); + if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() { + if (getKeyMap(options.keyMap) == startMap) { + options.keyMap = (next.call ? next.call(null, instance) : next); + } + }, 50); + + var name = keyNames[e_prop(e, "keyCode")], handled = false; + if (name == null || e.altGraphKey) return false; + if (e_prop(e, "altKey")) name = "Alt-" + name; + if (e_prop(e, "ctrlKey")) name = "Ctrl-" + name; + if (e_prop(e, "metaKey")) name = "Cmd-" + name; + + if (e_prop(e, "shiftKey")) + handled = lookupKey("Shift-" + name, options.extraKeys, options.keyMap, + function(b) {return doHandleBinding(b, true);}); + if (!handled) + handled = lookupKey(name, options.extraKeys, options.keyMap, doHandleBinding); + + if (handled) e_preventDefault(e); + return handled; + } + function handleCharBinding(e, ch) { + var handled = lookupKey("'" + ch + "'", options.extraKeys, + options.keyMap, doHandleBinding); + if (handled) e_preventDefault(e); + return handled; + } + + var lastStoppedKey = null, maybeTransition; function onKeyDown(e) { if (!focused) onFocus(); - var code = e.keyCode; + if (ie && e.keyCode == 27) { e.returnValue = false; } + if (pollingFast) { if (readInput()) pollingFast = false; } + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + var code = e_prop(e, "keyCode"); // IE does strange things with escape. - if (ie && code == 27) { e.returnValue = false; } - setShift(code == 16 || e.shiftKey); + setShift(code == 16 || e_prop(e, "shiftKey")); // First give onKeyEvent option a chance to handle this. - if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; var handled = handleKeyBinding(e); if (window.opera) { - lastStoppedKey = handled ? e.keyCode : null; + lastStoppedKey = handled ? code : null; // Opera has no cut event... we try to at least catch the key combo - if (!handled && (mac ? e.metaKey : e.ctrlKey) && e.keyCode == 88) + if (!handled && code == 88 && e_prop(e, mac ? "metaKey" : "ctrlKey")) replaceSelection(""); } } function onKeyPress(e) { - if (window.opera && e.keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} + if (pollingFast) readInput(); if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode"); + if (window.opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} if (window.opera && !e.which && handleKeyBinding(e)) return; - if (options.electricChars && mode.electricChars) { - var ch = String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode); + var ch = String.fromCharCode(charCode == null ? keyCode : charCode); + if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) { if (mode.electricChars.indexOf(ch) > -1) setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75); } + if (handleCharBinding(e, ch)) return; fastPoll(); } function onKeyUp(e) { if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; - if (e.keyCode == 16) shiftSelecting = null; + if (e_prop(e, "keyCode") == 16) shiftSelecting = null; } function onFocus() { - if (options.readOnly) return; + if (options.readOnly == "nocursor") return; if (!focused) { if (options.onFocus) options.onFocus(instance); focused = true; @@ -517,6 +575,10 @@ var CodeMirror = (function() { if (focused) { if (options.onBlur) options.onBlur(instance); focused = false; + if (bracketHighlighted) + operation(function(){ + if (bracketHighlighted) { bracketHighlighted(); bracketHighlighted = null; } + })(); wrapper.className = wrapper.className.replace(" CodeMirror-focused", ""); } clearInterval(blinker); @@ -526,6 +588,7 @@ var CodeMirror = (function() { // Replace the range from from to to by the strings in newText. // Afterwards, set the selection to selFrom, selTo. function updateLines(from, to, newText, selFrom, selTo) { + if (suppressEdits) return; if (history) { var old = []; doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); }); @@ -535,21 +598,25 @@ var CodeMirror = (function() { updateLinesNoUndo(from, to, newText, selFrom, selTo); } function unredoHelper(from, to) { - var change = from.pop(); - if (change) { + if (!from.length) return; + var set = from.pop(), out = []; + for (var i = set.length - 1; i >= 0; i -= 1) { + var change = set[i]; var replaced = [], end = change.start + change.added; doc.iter(change.start, end, function(line) { replaced.push(line.text); }); - to.push({start: change.start, added: change.old.length, old: replaced}); + out.push({start: change.start, added: change.old.length, old: replaced}); var pos = clipPos({line: change.start + change.old.length - 1, ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])}); updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos); - updateInput = true; } + updateInput = true; + to.push(out); } function undo() {unredoHelper(history.done, history.undone);} function redo() {unredoHelper(history.undone, history.done);} function updateLinesNoUndo(from, to, newText, selFrom, selTo) { + if (suppressEdits) return; var recomputeMaxLength = false, maxLineLength = maxLine.length; if (!options.lineWrapping) doc.iter(from.line, to.line, function(line) { @@ -651,7 +718,8 @@ var CodeMirror = (function() { setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line)); // Make sure the scroll-size div has the correct height. - code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + "px"; + if (scroller.clientHeight) + code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + "px"; } function replaceRange(code, from, to) { @@ -729,7 +797,7 @@ var CodeMirror = (function() { // supported or compatible enough yet to rely on.) var prevInput = ""; function readInput() { - if (leaveInputAlone || !focused || hasSelection(input)) return false; + if (leaveInputAlone || !focused || hasSelection(input) || options.readOnly) return false; var text = input.value; if (text == prevInput) return false; shiftSelecting = null; @@ -747,12 +815,12 @@ var CodeMirror = (function() { if (!posEq(sel.from, sel.to)) { prevInput = ""; input.value = getSelection(); - input.select(); + selectInput(input); } else if (user) prevInput = input.value = ""; } function focusInput() { - if (!options.readOnly) input.focus(); + if (options.readOnly != "nocursor") input.focus(); } function scrollEditorIntoView() { @@ -809,7 +877,7 @@ var CodeMirror = (function() { // Compute the new visible window var visible = visibleLines(); // Bail out if the visible area is already rendered and nothing changed. - if (changes !== true && changes.length == 0 && visible.from >= showingFrom && visible.to <= showingTo) return; + if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) return; var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100); if (showingFrom < from && from - showingFrom < 20) from = showingFrom; if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo); @@ -831,9 +899,9 @@ var CodeMirror = (function() { intact.sort(function(a, b) {return a.domStart - b.domStart;}); var th = textHeight(), gutterDisplay = gutter.style.display; - lineDiv.style.display = gutter.style.display = "none"; + lineDiv.style.display = "none"; patchDisplay(from, to, intact); - lineDiv.style.display = ""; + lineDiv.style.display = gutter.style.display = ""; // Position the mover div to align with the lines it's supposed // to be showing (which will cover the visible display) @@ -844,7 +912,8 @@ var CodeMirror = (function() { showingFrom = from; showingTo = to; displayOffset = heightAtLine(doc, from); mover.style.top = (displayOffset * th) + "px"; - code.style.height = (doc.height * th + 2 * paddingTop()) + "px"; + if (scroller.clientHeight) + code.style.height = (doc.height * th + 2 * paddingTop()) + "px"; // Since this is all rather error prone, it is honoured with the // only assertion in the whole file. @@ -854,14 +923,19 @@ var CodeMirror = (function() { if (options.lineWrapping) { maxWidth = scroller.clientWidth; - var curNode = lineDiv.firstChild; + var curNode = lineDiv.firstChild, heightChanged = false; doc.iter(showingFrom, showingTo, function(line) { if (!line.hidden) { var height = Math.round(curNode.offsetHeight / th) || 1; - if (line.height != height) {updateLineHeight(line, height); gutterDirty = true;} + if (line.height != height) { + updateLineHeight(line, height); + gutterDirty = heightChanged = true; + } } curNode = curNode.nextSibling; }); + if (heightChanged) + code.style.height = (doc.height * th + 2 * paddingTop()) + "px"; } else { if (maxWidth == null) maxWidth = stringWidth(maxLine); if (maxWidth > scroller.clientWidth) { @@ -875,7 +949,7 @@ var CodeMirror = (function() { } gutter.style.display = gutterDisplay; if (different || gutterDirty) updateGutter(); - updateCursor(); + updateSelection(); if (!suppressCallback && options.onUpdate) options.onUpdate(instance); return true; } @@ -922,21 +996,19 @@ var CodeMirror = (function() { } // This pass fills in the lines that actually changed. var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from; - var sfrom = sel.from.line, sto = sel.to.line, inSel = sfrom < from && sto >= from; var scratch = targetDocument.createElement("div"), newElt; doc.iter(from, to, function(line) { - var ch1 = null, ch2 = null; - if (inSel) { - ch1 = 0; - if (sto == j) {inSel = false; ch2 = sel.to.ch;} - } else if (sfrom == j) { - if (sto == j) {ch1 = sel.from.ch; ch2 = sel.to.ch;} - else {inSel = true; ch1 = sel.from.ch;} - } if (nextIntact && nextIntact.to == j) nextIntact = intact.shift(); if (!nextIntact || nextIntact.from > j) { - if (line.hidden) scratch.innerHTML = "
";
-          else scratch.innerHTML = line.getHTML(ch1, ch2, true, tabText);
+          if (line.hidden) var html = scratch.innerHTML = "
";
+          else {
+            var html = '
' + line.getHTML(makeTab) + '
'; + // Kludge to make sure the styled element lies behind the selection (by z-index) + if (line.className) + html = '
 
' + html + "
"; + } + scratch.innerHTML = html; lineDiv.insertBefore(scratch.firstChild, curNode); } else { curNode = curNode.nextSibling; @@ -975,18 +1047,39 @@ var CodeMirror = (function() { lineSpace.style.marginLeft = gutter.offsetWidth + "px"; gutterDirty = false; } - function updateCursor() { - var head = sel.inverted ? sel.from : sel.to, lh = textHeight(); - var pos = localCoords(head, true); + function updateSelection() { + var collapsed = posEq(sel.from, sel.to); + var fromPos = localCoords(sel.from, true); + var toPos = collapsed ? fromPos : localCoords(sel.to, true); + var headPos = sel.inverted ? fromPos : toPos, th = textHeight(); var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv); - inputDiv.style.top = (pos.y + lineOff.top - wrapOff.top) + "px"; - inputDiv.style.left = (pos.x + lineOff.left - wrapOff.left) + "px"; - if (posEq(sel.from, sel.to)) { - cursor.style.top = pos.y + "px"; - cursor.style.left = (options.lineWrapping ? Math.min(pos.x, lineSpace.offsetWidth) : pos.x) + "px"; + inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.y + lineOff.top - wrapOff.top)) + "px"; + inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.x + lineOff.left - wrapOff.left)) + "px"; + if (collapsed) { + cursor.style.top = headPos.y + "px"; + cursor.style.left = (options.lineWrapping ? Math.min(headPos.x, lineSpace.offsetWidth) : headPos.x) + "px"; cursor.style.display = ""; + selectionDiv.style.display = "none"; + } else { + var sameLine = fromPos.y == toPos.y, html = ""; + function add(left, top, right, height) { + html += '
'; + } + if (sel.from.ch && fromPos.y >= 0) { + var right = sameLine ? lineSpace.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; + 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; + cursor.style.display = "none"; + selectionDiv.style.display = ""; } - else cursor.style.display = "none"; } function setShift(val) { @@ -1019,30 +1112,20 @@ var CodeMirror = (function() { else if (posEq(from, sel.to)) sel.inverted = false; else if (posEq(to, sel.from)) sel.inverted = true; - // Some ugly logic used to only mark the lines that actually did - // see a change in selection as changed, rather than the whole - // selected range. - if (posEq(from, to)) { - if (!posEq(sel.from, sel.to)) - changes.push({from: oldFrom, to: oldTo + 1}); - } - else if (posEq(sel.from, sel.to)) { - changes.push({from: from.line, to: to.line + 1}); - } - else { - if (!posEq(from, sel.from)) { - if (from.line < oldFrom) - changes.push({from: from.line, to: Math.min(to.line, oldFrom) + 1}); - else - changes.push({from: oldFrom, to: Math.min(oldTo, from.line) + 1}); - } - if (!posEq(to, sel.to)) { - if (to.line < oldTo) - changes.push({from: Math.max(oldFrom, from.line), to: oldTo + 1}); - else - changes.push({from: Math.max(from.line, oldTo), to: to.line + 1}); + if (options.autoClearEmptyLines && posEq(sel.from, sel.to)) { + var head = sel.inverted ? from : to; + if (head.line != sel.from.line) { + 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; } @@ -1123,7 +1206,7 @@ var CodeMirror = (function() { 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 = scroller.clientHeight; + 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); setCursor(target.line, target.ch, true); @@ -1138,7 +1221,7 @@ var CodeMirror = (function() { setSelectionUser({line: pos.line, ch: start}, {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); @@ -1211,13 +1294,10 @@ var CodeMirror = (function() { } 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 makeTab(col) { + var w = options.tabSize - col % options.tabSize; + for (var str = '', i = 0; i < w; ++i) str += " "; + return {html: str + "", width: w}; } function themeChanged() { scroller.className = scroller.className.replace(/\s*cm-s-\w+/g, "") + @@ -1316,9 +1396,12 @@ var CodeMirror = (function() { if (line.hidden != hidden) { line.hidden = hidden; updateLineHeight(line, hidden ? 0 : 1); - if (hidden && (sel.from.line == no || sel.to.line == no)) - setSelection(skipHidden(sel.from, sel.from.line, sel.from.ch), - skipHidden(sel.to, sel.to.line, sel.to.ch)); + 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; + setSelection(from, to); + } return (gutterDirty = true); } }); @@ -1351,7 +1434,7 @@ var CodeMirror = (function() { if (x <= 0) return 0; var lineObj = getLine(line), text = lineObj.text; function getX(len) { - measure.innerHTML = "
" + lineObj.getHTML(null, null, false, tabText, len) + "
"; + measure.innerHTML = "
" + lineObj.getHTML(makeTab, len) + "
"; return measure.firstChild.firstChild.offsetWidth; } var from = 0, fromX = 0, to = text.length, toX; @@ -1377,19 +1460,20 @@ var CodeMirror = (function() { 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(null, null, false, tabText, ch) +
+      measure.innerHTML = "
" + line.getHTML(makeTab, ch) +
         '' + htmlEscape(line.text.charAt(ch) || " ") + "" +
         extra + "
"; var elt = document.getElementById("CodeMirror-temp-" + tempId); var top = elt.offsetTop, left = elt.offsetLeft; // Older IEs report zero offsets for spans directly after a wrap - if (ie && ch && top == 0 && left == 0) { + if (ie && top == 0 && left == 0) { var backup = document.createElement("span"); backup.innerHTML = "x"; elt.parentNode.insertBefore(backup, elt.nextSibling); @@ -1486,7 +1570,7 @@ var CodeMirror = (function() { return coordsChar(x - offL.left, y - offL.top); } function onContextMenu(e) { - var pos = posFromMouse(e); + var pos = posFromMouse(e), scrollPos = scroller.scrollTop; if (!pos || window.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); @@ -1499,12 +1583,13 @@ var CodeMirror = (function() { leaveInputAlone = true; var val = input.value = getSelection(); focusInput(); - input.select(); + selectInput(input); function rehide() { var newVal = splitLines(input.value).join("\n"); if (newVal != val) operation(replaceSelection)(newVal, "end"); inputDiv.style.position = "relative"; input.style.cssText = oldCSS; + if (ie_lt9) scroller.scrollTop = scrollPos; leaveInputAlone = false; resetInput(true); slowPoll(); @@ -1516,8 +1601,7 @@ var CodeMirror = (function() { mouseup(); setTimeout(rehide, 20); }, true); - } - else { + } else { setTimeout(rehide, 50); } } @@ -1667,7 +1751,7 @@ var CodeMirror = (function() { if (selectionChanged) reScroll = !scrollCursorIntoView(); if (changes.length) updated = updateDisplay(changes, true); else { - if (selectionChanged) updateCursor(); + if (selectionChanged) updateSelection(); if (gutterDirty) updateGutter(); } if (reScroll) scrollCursorIntoView(); @@ -1714,10 +1798,12 @@ var CodeMirror = (function() { theme: "default", indentUnit: 2, indentWithTabs: false, + smartIndent: true, tabSize: 4, keyMap: "default", extraKeys: null, electricChars: true, + autoClearEmptyLines: false, onKeyEvent: null, lineWrapping: false, lineNumbers: false, @@ -1740,7 +1826,8 @@ var CodeMirror = (function() { document: window.document }; - var mac = /Mac/.test(navigator.platform); + var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); + var mac = ios || /Mac/.test(navigator.platform); var win = /Win/.test(navigator.platform); // Known modes, by name and by MIME @@ -1752,19 +1839,20 @@ var CodeMirror = (function() { 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 (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) { - if (window.console) console.warn("No mode " + mname + " found, falling back to plain text."); + if (window.console) console.warn("No mode " + spec.name + " found, falling back to plain text."); return CodeMirror.getMode(options, "text/plain"); } - return mfactory(options, config || {}); + return mfactory(options, spec); }; CodeMirror.listModes = function() { var list = []; @@ -1867,23 +1955,30 @@ 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) { + 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; - } - return null; - } - return extraMap ? lookup(name, extraMap, map) : lookup(name, keyMap[map]); + if (found != null && handle(found)) return true; + if (map.catchall) return handle(map.catchall); + 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; + } + if (extraMap && lookup(extraMap)) return true; + return lookup(map); } function isModifierKey(event) { - var name = keyNames[event.keyCode]; + var name = keyNames[e_prop(event, "keyCode")]; return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; } @@ -2100,7 +2195,8 @@ var CodeMirror = (function() { this.stateAfter = null; if (mk) { var diff = text.length - (to - from); - for (var i = 0, mark = mk[i]; i < mk.length; ++i) { + 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);} } @@ -2227,73 +2323,88 @@ 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(sfrom, sto, includePre, tabText, endAt) { - var html = [], first = true; - if (includePre) - html.push(this.className ? '
': "
");
+    getHTML: function(makeTab, endAt) {
+      var html = [], first = true, col = 0;
       function span(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 (text.indexOf("\t") == -1) {
+          col += text.length;
+          var escaped = htmlEscape(text);
+        } else {
+          var escaped = "";
+          for (var pos = 0;;) {
+            var idx = text.indexOf("\t", pos);
+            if (idx == -1) {
+              escaped += htmlEscape(text.slice(pos));
+              col += text.length - pos;
+              break;
+            } else {
+              col += idx - pos;
+              var tab = makeTab(col);
+              escaped += htmlEscape(text.slice(pos, idx)) + tab.html;
+              col += tab.width;
+              pos = idx + 1;
+            }
+          }
+        }
+        if (style) html.push('', escaped, "");
+        else html.push(escaped);
       }
       var st = this.styles, allText = this.text, marked = this.marked;
-      if (sfrom == sto) sfrom = null;
       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(" ", sfrom != null && sto == null ? "CodeMirror-selected" : null);
-      else if (!marked && sfrom == null)
+        span(" ");
+      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, style && "cm-" + style);
+          span(str, styleToClass(style));
         }
       else {
         var pos = 0, i = 0, text = "", style, sg = 0;
-        var markpos = -1, mark = null;
-        function nextMark() {
-          if (marked) {
-            markpos += 1;
-            mark = (markpos < marked.length) ? marked[markpos] : null;
+        var nextChange = marked[0].from || 0, marks = [], markpos = 0;
+        function advanceMarks() {
+          var m;
+          while (markpos < marked.length &&
+                 ((m = marked[markpos]).from == pos || m.from == null)) {
+            if (m.style != null) 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;
+            if (to == pos) marks.splice(i--, 1);
+            else nextChange = Math.min(to, nextChange);
           }
         }
-        nextMark();
+        var m = 0;
         while (pos < len) {
-          var upto = len;
-          var extraStyle = "";
-          if (sfrom != null) {
-            if (sfrom > pos) upto = sfrom;
-            else if (sto == null || sto > pos) {
-              extraStyle = " CodeMirror-selected";
-              if (sto != null) upto = Math.min(upto, sto);
+          if (nextChange == pos) advanceMarks();
+          var upto = Math.min(len, nextChange);
+          while (true) {
+            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);
+              if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
+              pos = end;
             }
-          }
-          while (mark && mark.to != null && mark.to <= pos) nextMark();
-          if (mark) {
-            if (mark.from > pos) upto = Math.min(upto, mark.from);
-            else {
-              extraStyle += " " + mark.style;
-              if (mark.to != null) upto = Math.min(upto, mark.to);
-            }
-          }
-          for (;;) {
-            var end = pos + text.length;
-            var appliedStyle = style;
-            if (extraStyle) appliedStyle = style ? style + extraStyle : extraStyle;
-            span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle);
-            if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
-            pos = end;
-            text = st[i++]; style = "cm-" + st[i++];
+            text = st[i++]; style = styleToClass(st[i++]);
           }
         }
-        if (sfrom != null && sto == null) span(" ", "CodeMirror-selected");
       }
-      if (includePre) html.push("
"); return html.join(""); }, cleanUp: function() { @@ -2384,6 +2495,7 @@ var CodeMirror = (function() { var lines = []; this.collapse(lines); this.children = [new LeafChunk(lines)]; + this.children[0].parent = this; } }, collapse: function(lines) { @@ -2514,11 +2626,13 @@ var CodeMirror = (function() { History.prototype = { addChange: function(start, added, old) { this.undone.length = 0; - var time = +new Date, last = this.done[this.done.length - 1]; - if (time - this.time > 400 || !last || - last.start > start + added || last.start + last.added < start - last.added + last.old.length) - this.done.push({start: start, added: added, old: old}); - else { + var time = +new Date, cur = this.done[this.done.length - 1], last = cur && cur[cur.length - 1]; + var dtime = time - this.time; + if (dtime > 400 || !last) { + this.done.push([{start: start, added: added, old: old}]); + } else if (last.start > start + old.length || last.start + last.added < start - last.added + last.old.length) { + cur.push({start: start, added: added, old: old}); + } else { var oldoff = 0; if (start < last.start) { for (var i = last.start - start - 1; i >= 0; --i) @@ -2566,6 +2680,13 @@ var CodeMirror = (function() { else if (e.button & 4) return 2; } + // Allow 3rd-party code to override event properties by adding an override + // object to an event object. + function e_prop(e, prop) { + var overridden = e.override && e.override.hasOwnProperty(prop); + return overridden ? e.override[prop] : e[prop]; + } + // Event handler registration. If disconnect is true, it'll return a // function that unregisters the handler. function connect(node, type, handler, disconnect) { @@ -2584,6 +2705,8 @@ 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 @@ -2595,6 +2718,7 @@ var CodeMirror = (function() { var gecko = /gecko\/\d{7}/i.test(navigator.userAgent); var ie = /MSIE \d/.test(navigator.userAgent); + var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent); var webkit = /WebKit\//.test(navigator.userAgent); var lineSep = "\n"; @@ -2665,6 +2789,12 @@ var CodeMirror = (function() { function eltText(node) { return node.textContent || node.innerText || node.nodeValue || ""; } + function selectInput(node) { + if (ios) { // Mobile Safari apparently has a bug where select() is broken. + node.selectionStart = 0; + node.selectionEnd = node.value.length; + } else node.select(); + } // Operations on {line, ch} objects. function posEq(a, b) {return a.line == b.line && a.ch == b.ch;} @@ -2695,7 +2825,7 @@ var CodeMirror = (function() { // Used to position the cursor after an undo/redo by finding the // last edited character. function editEnd(from, to) { - if (!to) return from ? from.length : 0; + if (!to) return 0; if (!from) return to.length; for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j) if (from.charAt(i) != to.charAt(j)) break; -- cgit v1.2.3